├── CoreImageConvolutionExplorer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── simongladman.xcuserdatad │ └── xcschemes │ ├── CoreImageConvolutionExplorer.xcscheme │ └── xcschememanagement.plist ├── CoreImageConvolutionExplorer ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.swift ├── components │ ├── ConvolutionImageViewer.swift │ ├── KernelDetailView.swift │ └── KernelsTableView.swift ├── model │ └── CoreImageConvolutionExplorerModel.swift ├── railroad.jpg └── screenshot.PNG └── README.md /CoreImageConvolutionExplorer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3E2760C01CA96E3400C71B50 /* screenshot.PNG in Resources */ = {isa = PBXBuildFile; fileRef = 3E2760BF1CA96E3400C71B50 /* screenshot.PNG */; }; 11 | 3E9CD4FB1C9B0C3D00C67CB5 /* ConvolutionImageViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E9CD4FA1C9B0C3D00C67CB5 /* ConvolutionImageViewer.swift */; }; 12 | 3E9DEFB71CA3D3E700F9F241 /* railroad.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 3E9DEFB61CA3D3E700F9F241 /* railroad.jpg */; }; 13 | 3EABB9B61C9AC91400170E8F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EABB9B51C9AC91400170E8F /* AppDelegate.swift */; }; 14 | 3EABB9B81C9AC91400170E8F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EABB9B71C9AC91400170E8F /* ViewController.swift */; }; 15 | 3EABB9BB1C9AC91400170E8F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3EABB9B91C9AC91400170E8F /* Main.storyboard */; }; 16 | 3EABB9BD1C9AC91400170E8F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3EABB9BC1C9AC91400170E8F /* Assets.xcassets */; }; 17 | 3EABB9C01C9AC91400170E8F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3EABB9BE1C9AC91400170E8F /* LaunchScreen.storyboard */; }; 18 | 3EABB9CB1C9AC9CD00170E8F /* KernelsTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EABB9CA1C9AC9CD00170E8F /* KernelsTableView.swift */; }; 19 | 3EABB9CE1C9ACA9600170E8F /* CoreImageConvolutionExplorerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EABB9CD1C9ACA9600170E8F /* CoreImageConvolutionExplorerModel.swift */; }; 20 | 3EABB9D01C9AD17C00170E8F /* KernelDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EABB9CF1C9AD17C00170E8F /* KernelDetailView.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 3E2760BF1CA96E3400C71B50 /* screenshot.PNG */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = screenshot.PNG; path = CoreImageConvolutionExplorer/screenshot.PNG; sourceTree = ""; }; 25 | 3E9CD4FA1C9B0C3D00C67CB5 /* ConvolutionImageViewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ConvolutionImageViewer.swift; path = components/ConvolutionImageViewer.swift; sourceTree = ""; }; 26 | 3E9DEFB61CA3D3E700F9F241 /* railroad.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = railroad.jpg; sourceTree = ""; }; 27 | 3EABB9B21C9AC91400170E8F /* CoreImageConvolutionExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CoreImageConvolutionExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 3EABB9B51C9AC91400170E8F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 29 | 3EABB9B71C9AC91400170E8F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 30 | 3EABB9BA1C9AC91400170E8F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | 3EABB9BC1C9AC91400170E8F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | 3EABB9BF1C9AC91400170E8F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 33 | 3EABB9C11C9AC91400170E8F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | 3EABB9CA1C9AC9CD00170E8F /* KernelsTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = KernelsTableView.swift; path = components/KernelsTableView.swift; sourceTree = ""; }; 35 | 3EABB9CD1C9ACA9600170E8F /* CoreImageConvolutionExplorerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CoreImageConvolutionExplorerModel.swift; path = model/CoreImageConvolutionExplorerModel.swift; sourceTree = ""; }; 36 | 3EABB9CF1C9AD17C00170E8F /* KernelDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = KernelDetailView.swift; path = components/KernelDetailView.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | 3EABB9AF1C9AC91400170E8F /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 3EABB9A91C9AC91400170E8F = { 51 | isa = PBXGroup; 52 | children = ( 53 | 3E2760BF1CA96E3400C71B50 /* screenshot.PNG */, 54 | 3EABB9B41C9AC91400170E8F /* CoreImageConvolutionExplorer */, 55 | 3EABB9B31C9AC91400170E8F /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | 3EABB9B31C9AC91400170E8F /* Products */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 3EABB9B21C9AC91400170E8F /* CoreImageConvolutionExplorer.app */, 63 | ); 64 | name = Products; 65 | sourceTree = ""; 66 | }; 67 | 3EABB9B41C9AC91400170E8F /* CoreImageConvolutionExplorer */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 3EABB9CC1C9ACA7400170E8F /* model */, 71 | 3EABB9C91C9AC98000170E8F /* components */, 72 | 3E9DEFB61CA3D3E700F9F241 /* railroad.jpg */, 73 | 3EABB9B51C9AC91400170E8F /* AppDelegate.swift */, 74 | 3EABB9B71C9AC91400170E8F /* ViewController.swift */, 75 | 3EABB9B91C9AC91400170E8F /* Main.storyboard */, 76 | 3EABB9BC1C9AC91400170E8F /* Assets.xcassets */, 77 | 3EABB9BE1C9AC91400170E8F /* LaunchScreen.storyboard */, 78 | 3EABB9C11C9AC91400170E8F /* Info.plist */, 79 | ); 80 | path = CoreImageConvolutionExplorer; 81 | sourceTree = ""; 82 | }; 83 | 3EABB9C91C9AC98000170E8F /* components */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 3EABB9CA1C9AC9CD00170E8F /* KernelsTableView.swift */, 87 | 3EABB9CF1C9AD17C00170E8F /* KernelDetailView.swift */, 88 | 3E9CD4FA1C9B0C3D00C67CB5 /* ConvolutionImageViewer.swift */, 89 | ); 90 | name = components; 91 | sourceTree = ""; 92 | }; 93 | 3EABB9CC1C9ACA7400170E8F /* model */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 3EABB9CD1C9ACA9600170E8F /* CoreImageConvolutionExplorerModel.swift */, 97 | ); 98 | name = model; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 3EABB9B11C9AC91400170E8F /* CoreImageConvolutionExplorer */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 3EABB9C41C9AC91400170E8F /* Build configuration list for PBXNativeTarget "CoreImageConvolutionExplorer" */; 107 | buildPhases = ( 108 | 3EABB9AE1C9AC91400170E8F /* Sources */, 109 | 3EABB9AF1C9AC91400170E8F /* Frameworks */, 110 | 3EABB9B01C9AC91400170E8F /* Resources */, 111 | ); 112 | buildRules = ( 113 | ); 114 | dependencies = ( 115 | ); 116 | name = CoreImageConvolutionExplorer; 117 | productName = CoreImageConvolutionExplorer; 118 | productReference = 3EABB9B21C9AC91400170E8F /* CoreImageConvolutionExplorer.app */; 119 | productType = "com.apple.product-type.application"; 120 | }; 121 | /* End PBXNativeTarget section */ 122 | 123 | /* Begin PBXProject section */ 124 | 3EABB9AA1C9AC91400170E8F /* Project object */ = { 125 | isa = PBXProject; 126 | attributes = { 127 | LastSwiftUpdateCheck = 0720; 128 | LastUpgradeCheck = 0720; 129 | ORGANIZATIONNAME = "Simon Gladman"; 130 | TargetAttributes = { 131 | 3EABB9B11C9AC91400170E8F = { 132 | CreatedOnToolsVersion = 7.2.1; 133 | }; 134 | }; 135 | }; 136 | buildConfigurationList = 3EABB9AD1C9AC91400170E8F /* Build configuration list for PBXProject "CoreImageConvolutionExplorer" */; 137 | compatibilityVersion = "Xcode 3.2"; 138 | developmentRegion = English; 139 | hasScannedForEncodings = 0; 140 | knownRegions = ( 141 | en, 142 | Base, 143 | ); 144 | mainGroup = 3EABB9A91C9AC91400170E8F; 145 | productRefGroup = 3EABB9B31C9AC91400170E8F /* Products */; 146 | projectDirPath = ""; 147 | projectRoot = ""; 148 | targets = ( 149 | 3EABB9B11C9AC91400170E8F /* CoreImageConvolutionExplorer */, 150 | ); 151 | }; 152 | /* End PBXProject section */ 153 | 154 | /* Begin PBXResourcesBuildPhase section */ 155 | 3EABB9B01C9AC91400170E8F /* Resources */ = { 156 | isa = PBXResourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | 3EABB9C01C9AC91400170E8F /* LaunchScreen.storyboard in Resources */, 160 | 3EABB9BD1C9AC91400170E8F /* Assets.xcassets in Resources */, 161 | 3EABB9BB1C9AC91400170E8F /* Main.storyboard in Resources */, 162 | 3E9DEFB71CA3D3E700F9F241 /* railroad.jpg in Resources */, 163 | 3E2760C01CA96E3400C71B50 /* screenshot.PNG in Resources */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXResourcesBuildPhase section */ 168 | 169 | /* Begin PBXSourcesBuildPhase section */ 170 | 3EABB9AE1C9AC91400170E8F /* Sources */ = { 171 | isa = PBXSourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | 3EABB9CE1C9ACA9600170E8F /* CoreImageConvolutionExplorerModel.swift in Sources */, 175 | 3EABB9D01C9AD17C00170E8F /* KernelDetailView.swift in Sources */, 176 | 3EABB9CB1C9AC9CD00170E8F /* KernelsTableView.swift in Sources */, 177 | 3EABB9B81C9AC91400170E8F /* ViewController.swift in Sources */, 178 | 3E9CD4FB1C9B0C3D00C67CB5 /* ConvolutionImageViewer.swift in Sources */, 179 | 3EABB9B61C9AC91400170E8F /* AppDelegate.swift in Sources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXSourcesBuildPhase section */ 184 | 185 | /* Begin PBXVariantGroup section */ 186 | 3EABB9B91C9AC91400170E8F /* Main.storyboard */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | 3EABB9BA1C9AC91400170E8F /* Base */, 190 | ); 191 | name = Main.storyboard; 192 | sourceTree = ""; 193 | }; 194 | 3EABB9BE1C9AC91400170E8F /* LaunchScreen.storyboard */ = { 195 | isa = PBXVariantGroup; 196 | children = ( 197 | 3EABB9BF1C9AC91400170E8F /* Base */, 198 | ); 199 | name = LaunchScreen.storyboard; 200 | sourceTree = ""; 201 | }; 202 | /* End PBXVariantGroup section */ 203 | 204 | /* Begin XCBuildConfiguration section */ 205 | 3EABB9C21C9AC91400170E8F /* Debug */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | ALWAYS_SEARCH_USER_PATHS = NO; 209 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 210 | CLANG_CXX_LIBRARY = "libc++"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_WARN_BOOL_CONVERSION = YES; 214 | CLANG_WARN_CONSTANT_CONVERSION = YES; 215 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_UNREACHABLE_CODE = YES; 221 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 222 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 223 | COPY_PHASE_STRIP = NO; 224 | DEBUG_INFORMATION_FORMAT = dwarf; 225 | ENABLE_STRICT_OBJC_MSGSEND = YES; 226 | ENABLE_TESTABILITY = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu99; 228 | GCC_DYNAMIC_NO_PIC = NO; 229 | GCC_NO_COMMON_BLOCKS = YES; 230 | GCC_OPTIMIZATION_LEVEL = 0; 231 | GCC_PREPROCESSOR_DEFINITIONS = ( 232 | "DEBUG=1", 233 | "$(inherited)", 234 | ); 235 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 236 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 237 | GCC_WARN_UNDECLARED_SELECTOR = YES; 238 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 239 | GCC_WARN_UNUSED_FUNCTION = YES; 240 | GCC_WARN_UNUSED_VARIABLE = YES; 241 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 242 | MTL_ENABLE_DEBUG_INFO = YES; 243 | ONLY_ACTIVE_ARCH = YES; 244 | SDKROOT = iphoneos; 245 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 246 | TARGETED_DEVICE_FAMILY = 2; 247 | }; 248 | name = Debug; 249 | }; 250 | 3EABB9C31C9AC91400170E8F /* Release */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 255 | CLANG_CXX_LIBRARY = "libc++"; 256 | CLANG_ENABLE_MODULES = YES; 257 | CLANG_ENABLE_OBJC_ARC = YES; 258 | CLANG_WARN_BOOL_CONVERSION = YES; 259 | CLANG_WARN_CONSTANT_CONVERSION = YES; 260 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 261 | CLANG_WARN_EMPTY_BODY = YES; 262 | CLANG_WARN_ENUM_CONVERSION = YES; 263 | CLANG_WARN_INT_CONVERSION = YES; 264 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 268 | COPY_PHASE_STRIP = NO; 269 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 270 | ENABLE_NS_ASSERTIONS = NO; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_NO_COMMON_BLOCKS = YES; 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 281 | MTL_ENABLE_DEBUG_INFO = NO; 282 | SDKROOT = iphoneos; 283 | TARGETED_DEVICE_FAMILY = 2; 284 | VALIDATE_PRODUCT = YES; 285 | }; 286 | name = Release; 287 | }; 288 | 3EABB9C51C9AC91400170E8F /* Debug */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 292 | INFOPLIST_FILE = CoreImageConvolutionExplorer/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 294 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.CoreImageConvolutionExplorer; 295 | PRODUCT_NAME = "$(TARGET_NAME)"; 296 | }; 297 | name = Debug; 298 | }; 299 | 3EABB9C61C9AC91400170E8F /* Release */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 303 | INFOPLIST_FILE = CoreImageConvolutionExplorer/Info.plist; 304 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 305 | PRODUCT_BUNDLE_IDENTIFIER = uk.co.flexmonkey.CoreImageConvolutionExplorer; 306 | PRODUCT_NAME = "$(TARGET_NAME)"; 307 | }; 308 | name = Release; 309 | }; 310 | /* End XCBuildConfiguration section */ 311 | 312 | /* Begin XCConfigurationList section */ 313 | 3EABB9AD1C9AC91400170E8F /* Build configuration list for PBXProject "CoreImageConvolutionExplorer" */ = { 314 | isa = XCConfigurationList; 315 | buildConfigurations = ( 316 | 3EABB9C21C9AC91400170E8F /* Debug */, 317 | 3EABB9C31C9AC91400170E8F /* Release */, 318 | ); 319 | defaultConfigurationIsVisible = 0; 320 | defaultConfigurationName = Release; 321 | }; 322 | 3EABB9C41C9AC91400170E8F /* Build configuration list for PBXNativeTarget "CoreImageConvolutionExplorer" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | 3EABB9C51C9AC91400170E8F /* Debug */, 326 | 3EABB9C61C9AC91400170E8F /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | /* End XCConfigurationList section */ 332 | }; 333 | rootObject = 3EABB9AA1C9AC91400170E8F /* Project object */; 334 | } 335 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/CoreImageConvolutionExplorer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer.xcodeproj/xcuserdata/simongladman.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CoreImageConvolutionExplorer.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 3EABB9B11C9AC91400170E8F 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CoreImageConvolutionExplorer 4 | // 5 | // Created by Simon Gladman on 17/03/2016. 6 | // Copyright © 2016 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "ipad", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "ipad", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "ipad", 15 | "size" : "40x40", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "40x40", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "76x76", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "76x76", 31 | "scale" : "2x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIRequiresFullScreen 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CoreImageConvolutionExplorer 4 | // 5 | // Created by Simon Gladman on 17/03/2016. 6 | // Copyright © 2016 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController 12 | { 13 | let kernelsTableView = KernelsTableView() 14 | let kernelDetailView = KernelDetailView() 15 | let convolutionImageViewer = ConvolutionImageViewer() 16 | 17 | override func viewDidLoad() 18 | { 19 | super.viewDidLoad() 20 | 21 | view.addSubview(kernelsTableView) 22 | view.addSubview(kernelDetailView) 23 | view.addSubview(convolutionImageViewer) 24 | 25 | kernelsTableView.addTarget(self, action: #selector(ViewController.kernelSelectionChange), 26 | forControlEvents: .ValueChanged) 27 | } 28 | 29 | func kernelSelectionChange() 30 | { 31 | kernelDetailView.weights = kernelsTableView.weights 32 | convolutionImageViewer.weights = kernelsTableView.weights 33 | } 34 | 35 | override func viewDidLayoutSubviews() 36 | { 37 | let topMargin = topLayoutGuide.length 38 | let kernelsTableViewWidth = CGFloat(200) 39 | let workspaceWidth = view.frame.width - kernelsTableViewWidth 40 | let workspaceHeight = view.frame.height - topMargin 41 | 42 | kernelsTableView.frame = CGRect(x: 0, 43 | y: topMargin, 44 | width: kernelsTableViewWidth, 45 | height: workspaceHeight).insetBy(dx: 5, dy: 0) 46 | 47 | kernelDetailView.frame = CGRect(x: kernelsTableViewWidth, 48 | y: topMargin, 49 | width: workspaceWidth / 2, 50 | height: workspaceHeight).insetBy(dx: 5, dy: 0) 51 | 52 | convolutionImageViewer.frame = CGRect(x: kernelsTableViewWidth + workspaceWidth / 2, 53 | y: topMargin, 54 | width: workspaceWidth / 2, 55 | height: workspaceHeight).insetBy(dx: 5, dy: 0) 56 | } 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/components/ConvolutionImageViewer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConvolutionImageViewer.swift 3 | // CoreImageConvolutionExplorer 4 | // 5 | // Created by Simon Gladman on 17/03/2016. 6 | // Copyright © 2016 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GLKit 11 | 12 | class ConvolutionImageViewer: UIView 13 | { 14 | let railroadImage = CIImage(image: UIImage(named: "railroad.jpg")!)! 15 | 16 | let makeOpaqueKernel = CIColorKernel(string: "kernel vec4 makeOpaque(__sample pixel) { return vec4(pixel.rgb, 1.0); }") 17 | 18 | let imageView = OpenGLImageView() 19 | 20 | let biasSlider = LabelledSlider(title: "Bias", 21 | minimumValue: 0, 22 | maximumValue: 1) 23 | 24 | let normaliseButton = LabelledSwitch(title: "Normalize", 25 | on: true) 26 | 27 | let premultiplyButton = LabelledSwitch(title: "Premultiply", 28 | on: true) 29 | 30 | override init(frame: CGRect) 31 | { 32 | super.init(frame: frame) 33 | 34 | addSubview(imageView) 35 | addSubview(biasSlider) 36 | addSubview(normaliseButton) 37 | addSubview(premultiplyButton) 38 | 39 | imageView.image = railroadImage 40 | 41 | biasSlider.addTarget(self, 42 | action: #selector(ConvolutionImageViewer.applyConvolutionKernel), 43 | forControlEvents: .ValueChanged) 44 | 45 | normaliseButton.addTarget(self, 46 | action: #selector(ConvolutionImageViewer.applyConvolutionKernel), 47 | forControlEvents: .ValueChanged) 48 | 49 | premultiplyButton.addTarget(self, 50 | action: #selector(ConvolutionImageViewer.applyConvolutionKernel), 51 | forControlEvents: .ValueChanged) 52 | } 53 | 54 | required init?(coder aDecoder: NSCoder) 55 | { 56 | fatalError("init(coder:) has not been implemented") 57 | } 58 | 59 | override func didMoveToSuperview() 60 | { 61 | super.didMoveToSuperview() 62 | 63 | enabled = false 64 | } 65 | 66 | var weights: [CGFloat]? 67 | { 68 | didSet 69 | { 70 | if weights?.count != 9 && weights?.count != 25 && weights?.count != 49 71 | { 72 | fatalError("Weights array is wrong length!") 73 | } 74 | 75 | enabled = weights != nil 76 | 77 | applyConvolutionKernel() 78 | } 79 | } 80 | 81 | var enabled = false 82 | { 83 | didSet 84 | { 85 | biasSlider.enabled = enabled 86 | normaliseButton.enabled = enabled 87 | alpha = enabled ? 1 : 0.5 88 | } 89 | } 90 | 91 | func normaliseWeightsArray(weights: [CGFloat]?, normalise: Bool) -> [CGFloat]? 92 | { 93 | guard let weights = weights else 94 | { 95 | return nil 96 | } 97 | 98 | if !normalise 99 | { 100 | return weights 101 | } 102 | 103 | let sum = weights.reduce(0, combine: +) 104 | 105 | return sum == 0 ? 106 | weights : 107 | weights.map({ $0 / sum }) 108 | } 109 | 110 | func applyConvolutionKernel() 111 | { 112 | guard let weights = normaliseWeightsArray(weights, normalise: normaliseButton.on) else 113 | { 114 | return 115 | } 116 | 117 | let filterName: String 118 | 119 | switch weights.count 120 | { 121 | case 9: 122 | filterName = "CIConvolution3X3" 123 | case 25: 124 | filterName = "CIConvolution5X5" 125 | default: 126 | filterName = "CIConvolution7X7" 127 | } 128 | 129 | let weightsVector: CIVector = CIVector(values: weights, count: weights.count) 130 | 131 | let finalImage = railroadImage.imageByApplyingFilter(filterName, 132 | withInputParameters: [ 133 | kCIInputWeightsKey: weightsVector, 134 | kCIInputBiasKey: CGFloat(biasSlider.value)]).imageByCroppingToRect(railroadImage.extent) 135 | 136 | // Two seperate approaches to make opaque to mirror 137 | // book content. The CIColorMatrix technique could 138 | // can be replicated by chaning the `makeOpaqueKernel`'s 139 | // code to: 140 | // 141 | // 'return vec4(unpremultiply(pixel).rgb, 1.0)' 142 | if premultiplyButton.on 143 | { 144 | imageView.image = makeOpaqueKernel?.applyWithExtent(railroadImage.extent, 145 | arguments: [finalImage]) 146 | } 147 | else 148 | { 149 | imageView.image = finalImage 150 | .imageByApplyingFilter("CIColorMatrix", withInputParameters: [ "inputBiasVector": CIVector(x: 0, y: 0, z: 0, w: 1)]) 151 | .imageByCroppingToRect(finalImage.extent) 152 | } 153 | } 154 | 155 | override func layoutSubviews() 156 | { 157 | imageView.frame = bounds 158 | imageView.setNeedsDisplay() 159 | 160 | let buttonY = frame.height - biasSlider.intrinsicContentSize().height - normaliseButton.intrinsicContentSize().height - 20 161 | 162 | normaliseButton.frame = CGRect(x: 0, 163 | y: buttonY, 164 | width: frame.width / 2 - 5, 165 | height: normaliseButton.intrinsicContentSize().height) 166 | 167 | premultiplyButton.frame = CGRect(x: frame.width / 2 + 5, 168 | y: buttonY, 169 | width: frame.width / 2 - 5, 170 | height: normaliseButton.intrinsicContentSize().height) 171 | 172 | biasSlider.frame = CGRect(x: 0, 173 | y: frame.height - biasSlider.intrinsicContentSize().height - 10, 174 | width: frame.width, 175 | height: biasSlider.intrinsicContentSize().height) 176 | } 177 | } 178 | 179 | // ------------------------------------------------------ 180 | 181 | // MARK: LabelledControl 182 | 183 | class LabelledControl: UIControl 184 | { 185 | let label = UILabel(frame: CGRectZero) 186 | let title: String 187 | 188 | init(title: String) 189 | { 190 | self.title = title 191 | 192 | super.init(frame: CGRectZero) 193 | 194 | layer.cornerRadius = 5 195 | layer.borderColor = UIColor.lightGrayColor().CGColor 196 | layer.borderWidth = 1 197 | 198 | addSubview(label) 199 | 200 | updateLabel() 201 | } 202 | 203 | required init?(coder aDecoder: NSCoder) 204 | { 205 | fatalError("init(coder:) has not been implemented") 206 | } 207 | 208 | func updateLabel() 209 | { 210 | label.text = title 211 | } 212 | 213 | override func layoutSubviews() 214 | { 215 | label.frame = CGRect(x: 0, 216 | y: 0, 217 | width: frame.width, 218 | height: frame.midY).insetBy(dx: 5, dy: 5) 219 | } 220 | } 221 | 222 | // MARK: LabelledSwitch 223 | 224 | class LabelledSwitch: LabelledControl 225 | { 226 | private let onOffSwitch = UISwitch() 227 | 228 | required init(title: String, on: Bool) 229 | { 230 | super.init(title: title) 231 | 232 | onOffSwitch.on = on 233 | 234 | onOffSwitch.addTarget(self, 235 | action: #selector(LabelledSwitch.switchChangeHandler), 236 | forControlEvents: .ValueChanged) 237 | 238 | addSubview(onOffSwitch) 239 | } 240 | 241 | required init?(coder aDecoder: NSCoder) 242 | { 243 | fatalError("init(coder:) has not been implemented") 244 | } 245 | 246 | var on: Bool 247 | { 248 | set 249 | { 250 | onOffSwitch.on = on 251 | } 252 | get 253 | { 254 | return onOffSwitch.on 255 | } 256 | } 257 | 258 | func switchChangeHandler() 259 | { 260 | on = onOffSwitch.on 261 | 262 | sendActionsForControlEvents(.ValueChanged) 263 | } 264 | 265 | override func intrinsicContentSize() -> CGSize 266 | { 267 | return CGSize(width: label.intrinsicContentSize().width + onOffSwitch.intrinsicContentSize().width + 10, 268 | height: max(label.intrinsicContentSize().height, onOffSwitch.intrinsicContentSize().height) + 10) 269 | } 270 | 271 | override func layoutSubviews() 272 | { 273 | label.frame = CGRect(x: 0, 274 | y: 0, 275 | width: frame.midX, 276 | height: frame.height).insetBy(dx: 5, dy: 5) 277 | 278 | onOffSwitch.frame = CGRect(x: frame.width - onOffSwitch.intrinsicContentSize().width - 10, 279 | y: 0, 280 | width: frame.width / 2, 281 | height: frame.height).insetBy(dx: 5, dy: 5) 282 | } 283 | } 284 | 285 | // MARK: LabelledSlider 286 | 287 | class LabelledSlider: LabelledControl 288 | { 289 | private let slider = UISlider(frame: CGRectZero) 290 | 291 | required init(title: String, minimumValue: Float = 0, maximumValue: Float = 1) 292 | { 293 | super.init(title: title) 294 | 295 | slider.minimumValue = minimumValue 296 | slider.maximumValue = maximumValue 297 | 298 | slider.addTarget(self, 299 | action: #selector(LabelledSlider.sliderChangeHandler), 300 | forControlEvents: .ValueChanged) 301 | 302 | addSubview(slider) 303 | } 304 | 305 | required init(coder aDecoder: NSCoder) 306 | { 307 | fatalError("init(coder:) has not been implemented") 308 | } 309 | 310 | var value: Float = 0 311 | { 312 | didSet 313 | { 314 | slider.value = Float(value) 315 | updateLabel() 316 | } 317 | } 318 | 319 | func sliderChangeHandler() 320 | { 321 | value = slider.value 322 | 323 | sendActionsForControlEvents(.ValueChanged) 324 | } 325 | 326 | override func updateLabel() 327 | { 328 | label.text = title + ": " + (NSString(format: "%.3f", Float(value)) as String) 329 | } 330 | 331 | override func intrinsicContentSize() -> CGSize 332 | { 333 | return CGSize(width: 100, 334 | height: label.intrinsicContentSize().height + slider.intrinsicContentSize().height + 10) 335 | } 336 | 337 | override func layoutSubviews() 338 | { 339 | label.frame = CGRect(x: 0, 340 | y: 0, 341 | width: frame.width, 342 | height: frame.height / 2).insetBy(dx: 5, dy: 5) 343 | 344 | slider.frame = CGRect(x: 0, 345 | y: frame.height / 2, 346 | width: frame.width, 347 | height: frame.height / 2).insetBy(dx: 5, dy: 5) 348 | } 349 | } 350 | 351 | // ------------------------------------------------------ 352 | 353 | // MARK: OpenGLImageView 354 | 355 | class OpenGLImageView: GLKView 356 | { 357 | let eaglContext = EAGLContext(API: .OpenGLES2) 358 | 359 | lazy var ciContext: CIContext = 360 | { 361 | [unowned self] in 362 | 363 | return CIContext(EAGLContext: self.eaglContext, 364 | options: [kCIContextWorkingColorSpace: NSNull()]) 365 | }() 366 | 367 | override init(frame: CGRect) 368 | { 369 | super.init(frame: frame, context: eaglContext) 370 | 371 | context = self.eaglContext 372 | delegate = self 373 | } 374 | 375 | override init(frame: CGRect, context: EAGLContext) 376 | { 377 | fatalError("init(frame:, context:) has not been implemented") 378 | } 379 | 380 | required init?(coder aDecoder: NSCoder) 381 | { 382 | fatalError("init(coder:) has not been implemented") 383 | } 384 | 385 | /// The image to display 386 | var image: CIImage? 387 | { 388 | didSet 389 | { 390 | setNeedsDisplay() 391 | } 392 | } 393 | } 394 | 395 | extension OpenGLImageView: GLKViewDelegate 396 | { 397 | func glkView(view: GLKView, drawInRect rect: CGRect) 398 | { 399 | guard let image = image else 400 | { 401 | return 402 | } 403 | 404 | let targetRect = image.extent.aspectFitInRect( 405 | target: CGRect(origin: CGPointZero, 406 | size: CGSize(width: drawableWidth, 407 | height: drawableHeight))) 408 | 409 | let ciBackgroundColor = CIColor( 410 | color: backgroundColor ?? UIColor.whiteColor()) 411 | 412 | ciContext.drawImage(CIImage(color: ciBackgroundColor), 413 | inRect: CGRect(x: 0, 414 | y: 0, 415 | width: drawableWidth, 416 | height: drawableHeight), 417 | fromRect: CGRect(x: 0, 418 | y: 0, 419 | width: drawableWidth, 420 | height: drawableHeight)) 421 | 422 | ciContext.drawImage(image, 423 | inRect: targetRect, 424 | fromRect: image.extent) 425 | } 426 | } 427 | 428 | extension CGRect 429 | { 430 | func aspectFitInRect(target target: CGRect) -> CGRect 431 | { 432 | let scale: CGFloat = 433 | { 434 | let scale = target.width / self.width 435 | 436 | return self.height * scale <= target.height ? 437 | scale : 438 | target.height / self.height 439 | }() 440 | 441 | let width = self.width * scale 442 | let height = self.height * scale 443 | let x = target.midX - width / 2 444 | let y = target.midY - height / 2 445 | 446 | return CGRect(x: x, 447 | y: y, 448 | width: width, 449 | height: height) 450 | } 451 | } -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/components/KernelDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KernelDetailView.swift 3 | // CoreImageConvolutionExplorer 4 | // 5 | // Created by Simon Gladman on 17/03/2016. 6 | // Copyright © 2016 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class KernelDetailView: UIView 12 | { 13 | let totalLabel: UILabel = 14 | { 15 | let label = UILabel() 16 | 17 | label.font = UIFont.boldSystemFontOfSize(20) 18 | label.textAlignment = .Center 19 | label.text = "Select Kernel"; 20 | 21 | return label 22 | }() 23 | 24 | let labels: [UILabel] = 25 | { 26 | var array = [UILabel]() 27 | 28 | for _ in 0 ..< 49 29 | { 30 | let label = UILabel() 31 | 32 | label.backgroundColor = .darkGrayColor() 33 | label.textColor = .whiteColor() 34 | label.textAlignment = .Center 35 | label.adjustsFontSizeToFitWidth = true 36 | label.font = UIFont.boldSystemFontOfSize(20) 37 | 38 | array.append(label) 39 | } 40 | 41 | return array 42 | }() 43 | 44 | var weights: [CGFloat]? 45 | { 46 | didSet 47 | { 48 | if weights?.count != 9 && weights?.count != 25 && weights?.count != 49 49 | { 50 | fatalError("Weights array is wrong length!") 51 | } 52 | 53 | if let weights = weights 54 | { 55 | let total = weights.reduce(0, combine: +) 56 | 57 | totalLabel.textColor = total == 0 ? UIColor.yellowColor() : total < 0 ? UIColor.redColor() : UIColor.blackColor() 58 | totalLabel.backgroundColor = total == 0 ? UIColor.darkGrayColor() : UIColor.whiteColor() 59 | totalLabel.text = "Σ \(total)" 60 | } 61 | else 62 | { 63 | totalLabel.text = "" 64 | } 65 | 66 | updateWeightsGrid() 67 | } 68 | } 69 | 70 | override init(frame: CGRect) 71 | { 72 | super.init(frame: frame) 73 | 74 | labels.forEach 75 | { 76 | addSubview($0) 77 | } 78 | 79 | addSubview(totalLabel) 80 | } 81 | 82 | required init?(coder aDecoder: NSCoder) 83 | { 84 | fatalError("init(coder:) has not been implemented") 85 | } 86 | 87 | func updateWeightsGrid() 88 | { 89 | guard let weights = weights else 90 | { 91 | return 92 | } 93 | 94 | var sourceIndex = 0 95 | 96 | labels.enumerate().forEach 97 | { 98 | let x = $0 % 7 99 | let y = $0 / 7 100 | 101 | switch weights.count 102 | { 103 | case 9: 104 | if x > 1 && x < 5 && y > 1 && y < 5 105 | { 106 | $1.text = "\(weights[sourceIndex])" 107 | sourceIndex += 1 108 | } 109 | else 110 | { 111 | $1.text = "" 112 | } 113 | case 25: 114 | if x > 0 && x < 6 && y > 0 && y < 6 115 | { 116 | $1.text = "\(weights[sourceIndex])" 117 | sourceIndex += 1 118 | } 119 | else 120 | { 121 | $1.text = "" 122 | } 123 | default: 124 | $1.text = "\(weights[$0])" 125 | } 126 | } 127 | } 128 | 129 | override func layoutSubviews() 130 | { 131 | let boxSide = Int(min(frame.width, frame.height) / 7) 132 | let boxesTop = (Int(max(frame.width, frame.height)) - (boxSide * 7)) / 2 133 | 134 | labels.enumerate().forEach 135 | { 136 | let x = $0 % 7 137 | let y = $0 / 7 138 | 139 | $1.frame = CGRect(x: x * boxSide, 140 | y: boxesTop + y * boxSide, 141 | width: boxSide, 142 | height: boxSide).insetBy(dx: 0.5, dy: 0.5) 143 | } 144 | 145 | totalLabel.frame = CGRect(x: 0, y: 146 | frame.height - 10 - totalLabel.intrinsicContentSize().height, 147 | width: frame.width, 148 | height: totalLabel.intrinsicContentSize().height) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/components/KernelsTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KernelsTableView.swift 3 | // CoreImageConvolutionExplorer 4 | // 5 | // Created by Simon Gladman on 17/03/2016. 6 | // Copyright © 2016 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class KernelsTableView: UIControl 12 | { 13 | let model = CoreImageConvolutionExplorerModel() 14 | 15 | let tableView: UITableView = 16 | { 17 | let tableView = UITableView(frame: CGRectZero, 18 | style: UITableViewStyle.Plain) 19 | 20 | tableView.registerClass(UITableViewCell.self, 21 | forCellReuseIdentifier: "ItemRenderer") 22 | 23 | return tableView 24 | }() 25 | 26 | private (set) var weights: [CGFloat]? 27 | 28 | override init(frame: CGRect) 29 | { 30 | super.init(frame: frame) 31 | 32 | addSubview(tableView) 33 | tableView.dataSource = self 34 | tableView.delegate = self 35 | 36 | tableView.reloadData() 37 | } 38 | 39 | required init?(coder aDecoder: NSCoder) 40 | { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | override func layoutSubviews() 45 | { 46 | tableView.frame = bounds 47 | } 48 | } 49 | 50 | extension KernelsTableView: UITableViewDataSource 51 | { 52 | func numberOfSectionsInTableView(tableView: UITableView) -> Int 53 | { 54 | return 1 55 | } 56 | 57 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 58 | { 59 | return model.kernels.count 60 | } 61 | 62 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell 63 | { 64 | let cell = tableView.dequeueReusableCellWithIdentifier("ItemRenderer", 65 | forIndexPath: indexPath) 66 | 67 | cell.textLabel?.text = model.kernels[indexPath.row].name 68 | cell.textLabel?.numberOfLines = 2 69 | cell.textLabel?.textAlignment = .Center 70 | 71 | return cell 72 | } 73 | } 74 | 75 | extension KernelsTableView: UITableViewDelegate 76 | { 77 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) 78 | { 79 | weights = model.kernels[indexPath.row].weights 80 | 81 | sendActionsForControlEvents(.ValueChanged) 82 | } 83 | } -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/model/CoreImageConvolutionExplorerModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreImageConvolutionExplorerModel.swift 3 | // CoreImageConvolutionExplorer 4 | // 5 | // Created by Simon Gladman on 17/03/2016. 6 | // Copyright © 2016 Simon Gladman. All rights reserved. 7 | // 8 | 9 | import CoreGraphics 10 | 11 | struct Kernel 12 | { 13 | let name: String 14 | let weights: [CGFloat] 15 | } 16 | 17 | struct CoreImageConvolutionExplorerModel 18 | { 19 | let kernels = [ 20 | 21 | Kernel(name: "3 x 3 Identity", weights: [ 22 | 0,0,0, 23 | 0,1,0, 24 | 0,0,0]), 25 | 26 | Kernel(name: "3 x 3 Box Blur", weights: [ 27 | 1,1,1, 28 | 1,1,1, 29 | 1,1,1]), 30 | 31 | Kernel(name: "3 x 3 Sharpen", weights: [ 32 | 0, -1, 0, 33 | -1, 5, -1, 34 | 0, -1, 0 35 | ]), 36 | 37 | Kernel(name: "3 x 3 Sharper", weights: [ 38 | -1,-1,-1, 39 | -1, 9,-1, 40 | -1,-1,-1 41 | ]), 42 | 43 | Kernel(name: "3 x 3 Sharpest", weights: [ 44 | 1, 1, 1, 45 | 1, -7, 1, 46 | 1, 1, 1 47 | ]), 48 | 49 | Kernel(name: "3 x 3 Edge Detect", weights: [ 50 | -1,-1,-1, 51 | -1, 8,-1, 52 | -1,-1,-1 53 | ]), 54 | 55 | Kernel(name: "3 x 3 Laplacian", weights: [ 56 | 0, 1, 0, 57 | 1, -4,1, 58 | 0, 1, 0 59 | ]), 60 | 61 | Kernel(name: "3 x 3 Emboss", weights: [ 62 | -2, -1, 0, 63 | -1, 1, 1, 64 | 0, 1, 2]), 65 | 66 | Kernel(name: "3 x 3 Emboss (2)", weights: [ 67 | 2, 0, 0, 68 | 0, -1, 0, 69 | 0, 0, -1]), 70 | 71 | Kernel(name: "3 x 3 Sobel x", weights: [ 72 | -1, 0, 1, 73 | -2, 0, 2, 74 | -1, 0, 1]), 75 | 76 | Kernel(name: "3 x 3 Sobel y", weights: [ 77 | -1, -2, -1, 78 | 0, 0, 0, 79 | 1, 2, 1]), 80 | 81 | Kernel(name: "5 x 5 Box Blur", weights: [ 82 | 1,1,1,1,1, 83 | 1,1,1,1,1, 84 | 1,1,1,1,1, 85 | 1,1,1,1,1, 86 | 1,1,1,1,1]), 87 | 88 | Kernel(name: "5 x 5 Bias Demo Emboss (zero)", weights: [ 89 | 5,5,5,5,5, 90 | 5,0,0,0,-5, 91 | 5,0,0,0,-5, 92 | 5,0,0,0,-5, 93 | -5,-5,-5,-5,-5 94 | ]), 95 | 96 | Kernel(name: "5 x 5 Bias Demo Emboss (+ve)", weights: [ 97 | 5,5,5,5,5, 98 | 5,0,0,0,-5, 99 | 5,0,0,0,-5, 100 | 5,0,0,0,-5, 101 | 5,-5,-5,-5,-5 102 | ]), 103 | 104 | Kernel(name: "5 x 5 Laplacian", weights: [ 105 | -4, -1, 0, -1, -4, 106 | -1, 2, 3, 2, -1, 107 | 0, 3, 4, 3, 0, 108 | -1, 2, 3, 2, -1, 109 | -4, -1, 0, -1, -4 110 | ]), 111 | 112 | Kernel(name: "5 x 5 Gaussian Blur", weights: [ 113 | 1, 4, 7, 4, 1, 114 | 4, 16, 26, 16, 4, 115 | 7, 26, 41, 26, 7, 116 | 4, 16, 26, 16, 4, 117 | 1, 4, 7, 4, 1 118 | ]), 119 | 120 | Kernel(name: "5 x 5 Laplacian of Gaussian", weights: [ 121 | 0, 0, -1, 0, 0, 122 | 0, -1, -2, -1, 0, 123 | -1, -2, 16, -2, -1, 124 | 0, -1, -2, -1, 0, 125 | 0, 0, -1, 0, 0 126 | ]), 127 | 128 | Kernel(name: "5 x 5 Emboss", weights: [ 129 | -1, -1, -1, -1, 0, 130 | -1, -1, -1, 0, 1, 131 | -1, -1, 0, 1, 1, 132 | -1, 0, 1, 1, 0, 133 | 0, 1, 1, 1, 1 134 | ]), 135 | 136 | Kernel(name: "5 x 5 Unsharp", weights: [ 137 | 1, 4, 6, 4, 1, 138 | 4, 16, 24, 16, 4, 139 | 6, 24, -476, 24, 6, 140 | 4, 16, 24, 16, 4, 141 | 1, 4, 6, 4, 1, 142 | ]), 143 | 144 | Kernel(name: "5 x 5 Sharpen", weights: [ 145 | -1, -1, -1, -1, -1, 146 | -1, 2, 2, 2, -1, 147 | -1, 2, 8, 2, -1, 148 | -1, 2, 2, 2, -1, 149 | -1, -1, -1, -1, -1 150 | ]), 151 | 152 | Kernel(name: "5 x 5 Sobel x", weights: [ 153 | -1, -2, 0, 2, 1, 154 | -4, -8, 0, 8, 4, 155 | -6, -12, 0, 12, 6, 156 | -4, -8, 0, 8, 4, 157 | -1, -2, 0, 2, 1, 158 | ]), 159 | 160 | Kernel(name: "5 x 5 Sobel y", weights: [ 161 | -1, -4, -6, -4, -1, 162 | -2, -8, -12, -8, -2, 163 | 0, 0, 0, 0, 0, 164 | 2, 8, 12, 8, 2, 165 | 1, 4, 6, 4, 1 166 | ]), 167 | 168 | Kernel(name: "7 x 7 Box Blur", weights: [ 169 | 1,1,1,1,1,1,1, 170 | 1,1,1,1,1,1,1, 171 | 1,1,1,1,1,1,1, 172 | 1,1,1,1,1,1,1, 173 | 1,1,1,1,1,1,1, 174 | 1,1,1,1,1,1,1, 175 | 1,1,1,1,1,1,1]), 176 | 177 | Kernel(name: "7 x 7 Sobel y", weights: [ 178 | -1, -3, -6, -8, -6, -3, -1, 179 | -2, -6, -14, -18, -14, -6, -2, 180 | -3, -7, -15, -20, -15, -7, -3, 181 | 0, 0, 0, 0, 0, 0, 0, 182 | 3, 7, 15, 20, 15, 7, 3, 183 | 2, 6, 14, 18, 14, 6, 2, 184 | 1, 3, 6, 8, 6, 3, 1 185 | ]), 186 | 187 | Kernel(name: "7 x 7 Sobel x", weights: [ 188 | -1, -2, -3, 0, 3, 2, 1, 189 | -3, -6, -7, 0, 7, 6, 3, 190 | -6, -14, -15, 0, 15, 14, 6, 191 | -8, -18, -20, 0, 20, 18, 8, 192 | -6, -14, -15, 0, 15, 14, 6, 193 | -3, -6, -7, 0, 7, 6, 3, 194 | -1, -2, -3, 0, 3, 2, 1, 195 | ]), 196 | 197 | Kernel(name: "7 x 7 Edge Detect", weights: [ 198 | 0, 0, -1, -1, -1, 0, 0, 199 | 0, -1, -3, -3, -3, -1, 0, 200 | -1, -3, 0, 7, 0, -3, -1, 201 | -1, -3, 7, 25, 7, -3, -1, 202 | -1, -3, 0, 7, 0, -3, -1, 203 | 0, -1, -3, -3, -3, -1, 0, 204 | 0, 0, -1, -1, -1, 0, 0 205 | ]), 206 | 207 | Kernel(name: "7 x 7 Gaussian Blur", weights: [ 208 | 0, 0, 0, 5, 0, 0, 0, 209 | 0, 5, 18, 32, 18, 5, 0, 210 | 0, 18, 64, 100, 64, 18, 0, 211 | 5, 32, 100, 100, 100, 32, 5, 212 | 0, 18, 64, 100, 64, 18, 0, 213 | 0, 5, 18, 32, 18, 5, 0, 214 | 0, 0, 0, 5, 0, 0, 0 215 | ]), 216 | 217 | Kernel(name: "7 x 7 Laplacian", weights: [ 218 | -10, -5, -2, -1, -2, -5, -10, 219 | -5, 0, 3, 4, 3, 0, -5, 220 | -2, 3, 6, 7, 6, 3, -2, 221 | -1, 4, 7, 8, 7, 4, -1, 222 | -2, 3, 6, 7, 6, 3, -2, 223 | -5, 0, 3, 4, 3, 0, -5, 224 | -10, -5, -2, -1, -2, -5, -10 225 | ]) 226 | 227 | ].sort({$0.name < $1.name}) 228 | } 229 | -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/railroad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlexMonkey/CoreImageConvolutionExplorer/5dc35a3e269fa3953db49ed4cca95e2e9e238efe/CoreImageConvolutionExplorer/railroad.jpg -------------------------------------------------------------------------------- /CoreImageConvolutionExplorer/screenshot.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlexMonkey/CoreImageConvolutionExplorer/5dc35a3e269fa3953db49ed4cca95e2e9e238efe/CoreImageConvolutionExplorer/screenshot.PNG -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoreImageConvolutionExplorer 2 | Core Image Convolution Explorer 3 | 4 | ![CoreImageConvolutionExplorer/screenshot.PNG](CoreImageConvolutionExplorer/screenshot.PNG) 5 | 6 | An iPad app to explore 28 different convolution kernels including blurs, edege detections and embosses. The app includes controls for normalizing the weight values and setting bias. It also includes a toggle to either premultiply or unpremultiply colors when making opaque. 7 | 8 | This project is a companion to the convolution chapter of my book, *Core Image For Swift*. The book is available from: 9 | 10 | * [*Core Image for Swift* from iBooks Store](https://itunes.apple.com/us/book/core-image-for-swift/id1073029980?mt=13) 11 | * [*Core Image For Swift* from Gumroad](https://gumroad.com/l/CoreImageForSwift) 12 | --------------------------------------------------------------------------------