├── .gitignore ├── .gitmodules ├── .travis.yml ├── AccessibleVideo-Bridging-Header.h ├── AccessibleVideo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── AccessibleVideo.xcscmblueprint └── xcshareddata │ └── xcschemes │ └── AccessibleVideo.xcscheme ├── AccessibleVideo ├── AccessibleVideo.entitlements ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── BlurBuffer.swift ├── BufferTypes.swift ├── CameraController.swift ├── ColorBuffer.swift ├── FilterBuffer.swift ├── FilterModel.swift ├── FilterRenderer.swift ├── Filters.plist ├── Images.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-60@2x.png │ │ ├── Icon-76.png │ │ └── Icon-76@2x.png │ ├── Buttons │ │ ├── Light-Shadow.imageset │ │ │ ├── Contents.json │ │ │ ├── LightShadow.png │ │ │ ├── LightShadow@2x.png │ │ │ └── LightShadow@3x.png │ │ ├── Light.imageset │ │ │ ├── Contents.json │ │ │ ├── Light.png │ │ │ ├── Light@2x.png │ │ │ └── Light@3x.png │ │ ├── Settings-Shadow.imageset │ │ │ ├── Contents.json │ │ │ ├── SettingsShadow.png │ │ │ ├── SettingsShadow@2x.png │ │ │ └── SettingsShadow@3x.png │ │ ├── Settings.imageset │ │ │ ├── Contents.json │ │ │ ├── Settings.png │ │ │ ├── Settings@2x.png │ │ │ └── Settings@3x.png │ │ ├── Switch-Shadow.imageset │ │ │ ├── Contents.json │ │ │ ├── SwitchShadow.png │ │ │ ├── SwitchShadow@2x.png │ │ │ └── SwitchShadow@3x.png │ │ └── Switch.imageset │ │ │ ├── Contents.json │ │ │ ├── Switch.png │ │ │ ├── Switch@2x.png │ │ │ └── Switch@3x.png │ └── HUD │ │ ├── ColorFilter.imageset │ │ ├── Brush.png │ │ ├── Brush@2x.png │ │ ├── Brush@3x.png │ │ └── Contents.json │ │ ├── Lock.imageset │ │ ├── Contents.json │ │ ├── Lock.png │ │ ├── Lock@2x.png │ │ └── Lock@3x.png │ │ ├── Unlock.imageset │ │ ├── Contents.json │ │ ├── Unlock.png │ │ ├── Unlock@2x.png │ │ └── Unlock@3x.png │ │ └── VideoFilter.imageset │ │ ├── Contents.json │ │ ├── TV.png │ │ ├── TV@2x.png │ │ └── TV@3x.png ├── Info.plist ├── MainViewController.swift ├── MetalView.swift ├── SettingsViewController.swift └── Shaders │ ├── Base.metal │ ├── Blur.metal │ ├── Canny.metal │ ├── Colorblind.metal │ ├── Common.metal │ ├── Shaders.plist │ └── Sobel.metal └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | ######################### 2 | # .gitignore file for Xcode4 and Xcode5 Source projects 3 | # 4 | # Apple bugs, waiting for Apple to fix/respond: 5 | # 6 | # 15564624 - what does the xccheckout file in Xcode5 do? Where's the documentation? 7 | # 8 | # Version 2.1 9 | # For latest version, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects 10 | # 11 | # 2013 updates: 12 | # - fixed the broken "save personal Schemes" 13 | # - added line-by-line explanations for EVERYTHING (some were missing) 14 | # 15 | # NB: if you are storing "built" products, this WILL NOT WORK, 16 | # and you should use a different .gitignore (or none at all) 17 | # This file is for SOURCE projects, where there are many extra 18 | # files that we want to exclude 19 | # 20 | ######################### 21 | 22 | ##### 23 | # OS X temporary files that should never be committed 24 | # 25 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 26 | 27 | .DS_Store 28 | 29 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 30 | 31 | .Trashes 32 | 33 | # c.f. http://www.westwind.com/reference/os-x/invisibles.html 34 | 35 | *.swp 36 | 37 | # *.lock - this is used and abused by many editors for many different things. 38 | # For the main ones I use (e.g. Eclipse), it should be excluded 39 | # from source-control, but YMMV 40 | 41 | *.lock 42 | 43 | # 44 | # profile - REMOVED temporarily (on double-checking, this seems incorrect; I can't find it in OS X docs?) 45 | #profile 46 | 47 | 48 | #### 49 | # Xcode temporary files that should never be committed 50 | # 51 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this... 52 | 53 | *~.nib 54 | 55 | 56 | #### 57 | # Xcode build files - 58 | # 59 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" 60 | 61 | DerivedData/ 62 | 63 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build" 64 | 65 | build/ 66 | 67 | 68 | ##### 69 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 70 | # 71 | # This is complicated: 72 | # 73 | # SOMETIMES you need to put this file in version control. 74 | # Apple designed it poorly - if you use "custom executables", they are 75 | # saved in this file. 76 | # 99% of projects do NOT use those, so they do NOT want to version control this file. 77 | # ..but if you're in the 1%, comment out the line "*.pbxuser" 78 | 79 | # .pbxuser: http://lists.apple.com/archives/xcode-users/2004/Jan/msg00193.html 80 | 81 | *.pbxuser 82 | 83 | # .mode1v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 84 | 85 | *.mode1v3 86 | 87 | # .mode2v3: http://lists.apple.com/archives/xcode-users/2007/Oct/msg00465.html 88 | 89 | *.mode2v3 90 | 91 | # .perspectivev3: http://stackoverflow.com/questions/5223297/xcode-projects-what-is-a-perspectivev3-file 92 | 93 | *.perspectivev3 94 | 95 | # NB: also, whitelist the default ones, some projects need to use these 96 | !default.pbxuser 97 | !default.mode1v3 98 | !default.mode2v3 99 | !default.perspectivev3 100 | 101 | 102 | #### 103 | # Xcode 4 - semi-personal settings 104 | # 105 | # 106 | # OPTION 1: --------------------------------- 107 | # throw away ALL personal settings (including custom schemes! 108 | # - unless they are "shared") 109 | # 110 | # NB: this is exclusive with OPTION 2 below 111 | xcuserdata 112 | 113 | # OPTION 2: --------------------------------- 114 | # get rid of ALL personal settings, but KEEP SOME OF THEM 115 | # - NB: you must manually uncomment the bits you want to keep 116 | # 117 | # NB: this *requires* git v1.8.2 or above; you may need to upgrade to latest OS X, 118 | # or manually install git over the top of the OS X version 119 | # NB: this is exclusive with OPTION 1 above 120 | # 121 | #xcuserdata/**/* 122 | 123 | # (requires option 2 above): Personal Schemes 124 | # 125 | #!xcuserdata/**/xcschemes/* 126 | 127 | #### 128 | # XCode 4 workspaces - more detailed 129 | # 130 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :) 131 | # 132 | # Workspace layout is quite spammy. For reference: 133 | # 134 | # /(root)/ 135 | # /(project-name).xcodeproj/ 136 | # project.pbxproj 137 | # /project.xcworkspace/ 138 | # contents.xcworkspacedata 139 | # /xcuserdata/ 140 | # /(your name)/xcuserdatad/ 141 | # UserInterfaceState.xcuserstate 142 | # /xcsshareddata/ 143 | # /xcschemes/ 144 | # (shared scheme name).xcscheme 145 | # /xcuserdata/ 146 | # /(your name)/xcuserdatad/ 147 | # (private scheme).xcscheme 148 | # xcschememanagement.plist 149 | # 150 | # 151 | 152 | #### 153 | # Xcode 4 - Deprecated classes 154 | # 155 | # Allegedly, if you manually "deprecate" your classes, they get moved here. 156 | # 157 | # We're using source-control, so this is a "feature" that we do not want! 158 | 159 | *.moved-aside 160 | 161 | #### 162 | # Xcode 5 - VCS file 163 | # The data in this file not represent state of your project. 164 | # If you'll leave this file in git - you will have merge conflicts during 165 | # pull your cahnges to other's repo 166 | # 167 | *.xccheckout 168 | 169 | #### 170 | # UNKNOWN: recommended by others, but I can't discover what these files are 171 | # 172 | # ...none. Everything is now explained. 173 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "MBProgressHUD"] 2 | path = MBProgressHUD 3 | url = https://github.com/jdg/MBProgressHUD.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: objective-c 3 | xcode_project: AccessibleVideo.xcodeproj 4 | -------------------------------------------------------------------------------- /AccessibleVideo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "MBProgressHUD.h" -------------------------------------------------------------------------------- /AccessibleVideo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | DF048B2219E3CC9000064E81 /* Shaders.plist in Resources */ = {isa = PBXBuildFile; fileRef = DF048B2119E3CC9000064E81 /* Shaders.plist */; }; 11 | DF048B2619E3F4CE00064E81 /* Sobel.metal in Sources */ = {isa = PBXBuildFile; fileRef = DF048B2519E3F4CE00064E81 /* Sobel.metal */; }; 12 | DF317CC31C0A909700CEF3D2 /* Filters.plist in Resources */ = {isa = PBXBuildFile; fileRef = DF317CC21C0A909700CEF3D2 /* Filters.plist */; }; 13 | DF317CC71C0BC5BB00CEF3D2 /* FilterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF317CC61C0BC5BB00CEF3D2 /* FilterModel.swift */; }; 14 | DF58E3F71A47EBF900020B1C /* FilterBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF58E3F61A47EBF900020B1C /* FilterBuffer.swift */; }; 15 | DF6A115019C8091C00D20618 /* Base.metal in Sources */ = {isa = PBXBuildFile; fileRef = DF6A114F19C8091C00D20618 /* Base.metal */; }; 16 | DF6A115419C8109F00D20618 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF6A115319C8109F00D20618 /* AVFoundation.framework */; }; 17 | DF6A115619C810B000D20618 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF6A115519C810B000D20618 /* CoreVideo.framework */; }; 18 | DF847FD91A11FE9A00AD6068 /* Canny.metal in Sources */ = {isa = PBXBuildFile; fileRef = DF847FD81A11FE9A00AD6068 /* Canny.metal */; }; 19 | DF9C846A1A4EB68A009C962B /* ColorBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF9C84691A4EB68A009C962B /* ColorBuffer.swift */; }; 20 | DF9C846C1A4F5C50009C962B /* BufferTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF9C846B1A4F5C50009C962B /* BufferTypes.swift */; }; 21 | DF9C846F1A4F66CE009C962B /* BlurBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF9C846E1A4F66CE009C962B /* BlurBuffer.swift */; }; 22 | DFAF7A0C19FFFC1800ED5B76 /* MBProgressHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = DFAF7A0B19FFFC1800ED5B76 /* MBProgressHUD.m */; }; 23 | DFC4CFEB1A3A1ACE00964650 /* Colorblind.metal in Sources */ = {isa = PBXBuildFile; fileRef = DFC4CFEA1A3A1ACE00964650 /* Colorblind.metal */; }; 24 | DFC4CFED1A3A1B6D00964650 /* Common.metal in Sources */ = {isa = PBXBuildFile; fileRef = DFC4CFEC1A3A1B6D00964650 /* Common.metal */; }; 25 | DFD2200B19C643BC00913975 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD2200A19C643BC00913975 /* AppDelegate.swift */; }; 26 | DFD2200D19C643BC00913975 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD2200C19C643BC00913975 /* MainViewController.swift */; }; 27 | DFD2201019C643BC00913975 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DFD2200E19C643BC00913975 /* Main.storyboard */; }; 28 | DFD2201219C643BC00913975 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DFD2201119C643BC00913975 /* Images.xcassets */; }; 29 | DFD2201519C643BC00913975 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = DFD2201319C643BC00913975 /* LaunchScreen.xib */; }; 30 | DFD2202B19C6462800913975 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFD2202A19C6462800913975 /* Metal.framework */; }; 31 | DFD2202D19C6478D00913975 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFD2202C19C6478D00913975 /* QuartzCore.framework */; }; 32 | DFE69F2F19E0EE140072EFAC /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFE69F2E19E0EE140072EFAC /* CameraController.swift */; }; 33 | DFE69F3119E0EE870072EFAC /* FilterRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFE69F3019E0EE870072EFAC /* FilterRenderer.swift */; }; 34 | DFF6E11A1A104E36009A3CD8 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFF6E1191A104E36009A3CD8 /* SettingsViewController.swift */; }; 35 | DFF6E11E1A10796D009A3CD8 /* Blur.metal in Sources */ = {isa = PBXBuildFile; fileRef = DFF6E11D1A10796D009A3CD8 /* Blur.metal */; }; 36 | DFFEE88F19E207B700D19FFA /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFFEE88E19E207B700D19FFA /* MetalView.swift */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | DF048B2119E3CC9000064E81 /* Shaders.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Shaders.plist; path = Shaders/Shaders.plist; sourceTree = ""; }; 41 | DF048B2519E3F4CE00064E81 /* Sobel.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; name = Sobel.metal; path = Shaders/Sobel.metal; sourceTree = ""; }; 42 | DF317CC21C0A909700CEF3D2 /* Filters.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Filters.plist; sourceTree = ""; }; 43 | DF317CC61C0BC5BB00CEF3D2 /* FilterModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterModel.swift; sourceTree = ""; }; 44 | DF58E3F61A47EBF900020B1C /* FilterBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterBuffer.swift; sourceTree = ""; }; 45 | DF6A114F19C8091C00D20618 /* Base.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; name = Base.metal; path = Shaders/Base.metal; sourceTree = ""; }; 46 | DF6A115319C8109F00D20618 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 47 | DF6A115519C810B000D20618 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; 48 | DF847FD71A11B48900AD6068 /* AccessibleVideo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = AccessibleVideo.entitlements; sourceTree = ""; }; 49 | DF847FD81A11FE9A00AD6068 /* Canny.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; name = Canny.metal; path = Shaders/Canny.metal; sourceTree = ""; }; 50 | DF9C84691A4EB68A009C962B /* ColorBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorBuffer.swift; sourceTree = ""; }; 51 | DF9C846B1A4F5C50009C962B /* BufferTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BufferTypes.swift; sourceTree = ""; }; 52 | DF9C846E1A4F66CE009C962B /* BlurBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlurBuffer.swift; sourceTree = ""; }; 53 | DFAF7A0919FFFC1800ED5B76 /* AccessibleVideo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AccessibleVideo-Bridging-Header.h"; sourceTree = ""; }; 54 | DFAF7A0A19FFFC1800ED5B76 /* MBProgressHUD.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MBProgressHUD.h; path = MBProgressHUD/MBProgressHUD.h; sourceTree = ""; }; 55 | DFAF7A0B19FFFC1800ED5B76 /* MBProgressHUD.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MBProgressHUD.m; path = MBProgressHUD/MBProgressHUD.m; sourceTree = ""; }; 56 | DFC4CFEA1A3A1ACE00964650 /* Colorblind.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; name = Colorblind.metal; path = Shaders/Colorblind.metal; sourceTree = ""; }; 57 | DFC4CFEC1A3A1B6D00964650 /* Common.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; name = Common.metal; path = Shaders/Common.metal; sourceTree = ""; }; 58 | DFD2200519C643BC00913975 /* AccessibleVideo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AccessibleVideo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | DFD2200919C643BC00913975 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | DFD2200A19C643BC00913975 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 61 | DFD2200C19C643BC00913975 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 62 | DFD2200F19C643BC00913975 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 63 | DFD2201119C643BC00913975 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 64 | DFD2201419C643BC00913975 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 65 | DFD2202A19C6462800913975 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; 66 | DFD2202C19C6478D00913975 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 67 | DFE69F2E19E0EE140072EFAC /* CameraController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = ""; }; 68 | DFE69F3019E0EE870072EFAC /* FilterRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterRenderer.swift; sourceTree = ""; }; 69 | DFF6E1191A104E36009A3CD8 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 70 | DFF6E11D1A10796D009A3CD8 /* Blur.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; name = Blur.metal; path = Shaders/Blur.metal; sourceTree = ""; }; 71 | DFFEE88E19E207B700D19FFA /* MetalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = ""; }; 72 | /* End PBXFileReference section */ 73 | 74 | /* Begin PBXFrameworksBuildPhase section */ 75 | DFD2200219C643BC00913975 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | DF6A115619C810B000D20618 /* CoreVideo.framework in Frameworks */, 80 | DF6A115419C8109F00D20618 /* AVFoundation.framework in Frameworks */, 81 | DFD2202D19C6478D00913975 /* QuartzCore.framework in Frameworks */, 82 | DFD2202B19C6462800913975 /* Metal.framework in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | DF6A115119C8092600D20618 /* Shaders */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | DF6A114F19C8091C00D20618 /* Base.metal */, 93 | DF048B2119E3CC9000064E81 /* Shaders.plist */, 94 | DF048B2519E3F4CE00064E81 /* Sobel.metal */, 95 | DFF6E11D1A10796D009A3CD8 /* Blur.metal */, 96 | DF847FD81A11FE9A00AD6068 /* Canny.metal */, 97 | DFC4CFEA1A3A1ACE00964650 /* Colorblind.metal */, 98 | DFC4CFEC1A3A1B6D00964650 /* Common.metal */, 99 | ); 100 | name = Shaders; 101 | sourceTree = ""; 102 | }; 103 | DF6A115219C8108E00D20618 /* Frameworks */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | DF6A115519C810B000D20618 /* CoreVideo.framework */, 107 | DF6A115319C8109F00D20618 /* AVFoundation.framework */, 108 | DFD2202C19C6478D00913975 /* QuartzCore.framework */, 109 | DFD2202A19C6462800913975 /* Metal.framework */, 110 | ); 111 | name = Frameworks; 112 | sourceTree = ""; 113 | }; 114 | DF9C846D1A4F634E009C962B /* Metal Renderer */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | DF9C846E1A4F66CE009C962B /* BlurBuffer.swift */, 118 | DF9C846B1A4F5C50009C962B /* BufferTypes.swift */, 119 | DF9C84691A4EB68A009C962B /* ColorBuffer.swift */, 120 | DF58E3F61A47EBF900020B1C /* FilterBuffer.swift */, 121 | DFE69F3019E0EE870072EFAC /* FilterRenderer.swift */, 122 | ); 123 | name = "Metal Renderer"; 124 | sourceTree = ""; 125 | }; 126 | DFAF7A0819FFFC0900ED5B76 /* MBProgressHUD */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | DFAF7A0A19FFFC1800ED5B76 /* MBProgressHUD.h */, 130 | DFAF7A0B19FFFC1800ED5B76 /* MBProgressHUD.m */, 131 | DFAF7A0919FFFC1800ED5B76 /* AccessibleVideo-Bridging-Header.h */, 132 | ); 133 | name = MBProgressHUD; 134 | sourceTree = ""; 135 | }; 136 | DFD21FFC19C643BC00913975 = { 137 | isa = PBXGroup; 138 | children = ( 139 | DFAF7A0819FFFC0900ED5B76 /* MBProgressHUD */, 140 | DF6A115219C8108E00D20618 /* Frameworks */, 141 | DFD2200719C643BC00913975 /* AccessibleVideo */, 142 | DFD2200619C643BC00913975 /* Products */, 143 | ); 144 | sourceTree = ""; 145 | }; 146 | DFD2200619C643BC00913975 /* Products */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | DFD2200519C643BC00913975 /* AccessibleVideo.app */, 150 | ); 151 | name = Products; 152 | sourceTree = ""; 153 | }; 154 | DFD2200719C643BC00913975 /* AccessibleVideo */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | DFD2200A19C643BC00913975 /* AppDelegate.swift */, 158 | DFE69F2E19E0EE140072EFAC /* CameraController.swift */, 159 | DFD2200E19C643BC00913975 /* Main.storyboard */, 160 | DFD2200C19C643BC00913975 /* MainViewController.swift */, 161 | DF9C846D1A4F634E009C962B /* Metal Renderer */, 162 | DFFEE88E19E207B700D19FFA /* MetalView.swift */, 163 | DFF6E1191A104E36009A3CD8 /* SettingsViewController.swift */, 164 | DF6A115119C8092600D20618 /* Shaders */, 165 | DFD2200819C643BC00913975 /* Supporting Files */, 166 | DF317CC21C0A909700CEF3D2 /* Filters.plist */, 167 | DF317CC61C0BC5BB00CEF3D2 /* FilterModel.swift */, 168 | ); 169 | path = AccessibleVideo; 170 | sourceTree = ""; 171 | }; 172 | DFD2200819C643BC00913975 /* Supporting Files */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | DFD2201119C643BC00913975 /* Images.xcassets */, 176 | DF847FD71A11B48900AD6068 /* AccessibleVideo.entitlements */, 177 | DFD2201319C643BC00913975 /* LaunchScreen.xib */, 178 | DFD2200919C643BC00913975 /* Info.plist */, 179 | ); 180 | name = "Supporting Files"; 181 | sourceTree = ""; 182 | }; 183 | /* End PBXGroup section */ 184 | 185 | /* Begin PBXNativeTarget section */ 186 | DFD2200419C643BC00913975 /* AccessibleVideo */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = DFD2202419C643BC00913975 /* Build configuration list for PBXNativeTarget "AccessibleVideo" */; 189 | buildPhases = ( 190 | DFD2200119C643BC00913975 /* Sources */, 191 | DFD2200219C643BC00913975 /* Frameworks */, 192 | DFD2200319C643BC00913975 /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | ); 198 | name = AccessibleVideo; 199 | productName = AccessibleVideo; 200 | productReference = DFD2200519C643BC00913975 /* AccessibleVideo.app */; 201 | productType = "com.apple.product-type.application"; 202 | }; 203 | /* End PBXNativeTarget section */ 204 | 205 | /* Begin PBXProject section */ 206 | DFD21FFD19C643BC00913975 /* Project object */ = { 207 | isa = PBXProject; 208 | attributes = { 209 | LastSwiftUpdateCheck = 0700; 210 | LastUpgradeCheck = 0800; 211 | ORGANIZATIONNAME = "Luke Groeninger"; 212 | TargetAttributes = { 213 | DFD2200419C643BC00913975 = { 214 | CreatedOnToolsVersion = 6.0; 215 | DevelopmentTeam = G8X4XMPVS9; 216 | LastSwiftMigration = 0820; 217 | ProvisioningStyle = Manual; 218 | SystemCapabilities = { 219 | com.apple.iCloud = { 220 | enabled = 1; 221 | }; 222 | }; 223 | }; 224 | }; 225 | }; 226 | buildConfigurationList = DFD2200019C643BC00913975 /* Build configuration list for PBXProject "AccessibleVideo" */; 227 | compatibilityVersion = "Xcode 3.2"; 228 | developmentRegion = English; 229 | hasScannedForEncodings = 0; 230 | knownRegions = ( 231 | en, 232 | Base, 233 | ); 234 | mainGroup = DFD21FFC19C643BC00913975; 235 | productRefGroup = DFD2200619C643BC00913975 /* Products */; 236 | projectDirPath = ""; 237 | projectRoot = ""; 238 | targets = ( 239 | DFD2200419C643BC00913975 /* AccessibleVideo */, 240 | ); 241 | }; 242 | /* End PBXProject section */ 243 | 244 | /* Begin PBXResourcesBuildPhase section */ 245 | DFD2200319C643BC00913975 /* Resources */ = { 246 | isa = PBXResourcesBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | DF317CC31C0A909700CEF3D2 /* Filters.plist in Resources */, 250 | DFD2201019C643BC00913975 /* Main.storyboard in Resources */, 251 | DF048B2219E3CC9000064E81 /* Shaders.plist in Resources */, 252 | DFD2201519C643BC00913975 /* LaunchScreen.xib in Resources */, 253 | DFD2201219C643BC00913975 /* Images.xcassets in Resources */, 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | /* End PBXResourcesBuildPhase section */ 258 | 259 | /* Begin PBXSourcesBuildPhase section */ 260 | DFD2200119C643BC00913975 /* Sources */ = { 261 | isa = PBXSourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | DFFEE88F19E207B700D19FFA /* MetalView.swift in Sources */, 265 | DFF6E11E1A10796D009A3CD8 /* Blur.metal in Sources */, 266 | DFE69F2F19E0EE140072EFAC /* CameraController.swift in Sources */, 267 | DF317CC71C0BC5BB00CEF3D2 /* FilterModel.swift in Sources */, 268 | DF6A115019C8091C00D20618 /* Base.metal in Sources */, 269 | DF048B2619E3F4CE00064E81 /* Sobel.metal in Sources */, 270 | DF58E3F71A47EBF900020B1C /* FilterBuffer.swift in Sources */, 271 | DF9C846A1A4EB68A009C962B /* ColorBuffer.swift in Sources */, 272 | DFC4CFEB1A3A1ACE00964650 /* Colorblind.metal in Sources */, 273 | DFD2200D19C643BC00913975 /* MainViewController.swift in Sources */, 274 | DF9C846F1A4F66CE009C962B /* BlurBuffer.swift in Sources */, 275 | DFE69F3119E0EE870072EFAC /* FilterRenderer.swift in Sources */, 276 | DFD2200B19C643BC00913975 /* AppDelegate.swift in Sources */, 277 | DF9C846C1A4F5C50009C962B /* BufferTypes.swift in Sources */, 278 | DF847FD91A11FE9A00AD6068 /* Canny.metal in Sources */, 279 | DFC4CFED1A3A1B6D00964650 /* Common.metal in Sources */, 280 | DFAF7A0C19FFFC1800ED5B76 /* MBProgressHUD.m in Sources */, 281 | DFF6E11A1A104E36009A3CD8 /* SettingsViewController.swift in Sources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | /* End PBXSourcesBuildPhase section */ 286 | 287 | /* Begin PBXVariantGroup section */ 288 | DFD2200E19C643BC00913975 /* Main.storyboard */ = { 289 | isa = PBXVariantGroup; 290 | children = ( 291 | DFD2200F19C643BC00913975 /* Base */, 292 | ); 293 | name = Main.storyboard; 294 | sourceTree = ""; 295 | }; 296 | DFD2201319C643BC00913975 /* LaunchScreen.xib */ = { 297 | isa = PBXVariantGroup; 298 | children = ( 299 | DFD2201419C643BC00913975 /* Base */, 300 | ); 301 | name = LaunchScreen.xib; 302 | sourceTree = ""; 303 | }; 304 | /* End PBXVariantGroup section */ 305 | 306 | /* Begin XCBuildConfiguration section */ 307 | DFD2202219C643BC00913975 /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ALWAYS_SEARCH_USER_PATHS = NO; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_CONSTANT_CONVERSION = YES; 317 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 318 | CLANG_WARN_EMPTY_BODY = YES; 319 | CLANG_WARN_ENUM_CONVERSION = YES; 320 | CLANG_WARN_INFINITE_RECURSION = YES; 321 | CLANG_WARN_INT_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 324 | CLANG_WARN_UNREACHABLE_CODE = YES; 325 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 326 | CODE_SIGN_IDENTITY = "iPhone Developer"; 327 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 328 | COPY_PHASE_STRIP = NO; 329 | ENABLE_STRICT_OBJC_MSGSEND = YES; 330 | ENABLE_TESTABILITY = YES; 331 | GCC_C_LANGUAGE_STANDARD = gnu99; 332 | GCC_DYNAMIC_NO_PIC = NO; 333 | GCC_NO_COMMON_BLOCKS = YES; 334 | GCC_OPTIMIZATION_LEVEL = 0; 335 | GCC_PREPROCESSOR_DEFINITIONS = ( 336 | "DEBUG=1", 337 | "$(inherited)", 338 | ); 339 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 347 | MTL_ENABLE_DEBUG_INFO = YES; 348 | ONLY_ACTIVE_ARCH = YES; 349 | PROVISIONING_PROFILE = ""; 350 | SDKROOT = iphoneos; 351 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 352 | TARGETED_DEVICE_FAMILY = "1,2"; 353 | }; 354 | name = Debug; 355 | }; 356 | DFD2202319C643BC00913975 /* Release */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | ALWAYS_SEARCH_USER_PATHS = NO; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BOOL_CONVERSION = YES; 365 | CLANG_WARN_CONSTANT_CONVERSION = YES; 366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INFINITE_RECURSION = YES; 370 | CLANG_WARN_INT_CONVERSION = YES; 371 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 372 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 373 | CLANG_WARN_UNREACHABLE_CODE = YES; 374 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 375 | CODE_SIGN_IDENTITY = "iPhone Developer"; 376 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 377 | COPY_PHASE_STRIP = YES; 378 | ENABLE_NS_ASSERTIONS = NO; 379 | ENABLE_STRICT_OBJC_MSGSEND = YES; 380 | GCC_C_LANGUAGE_STANDARD = gnu99; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 384 | GCC_WARN_UNDECLARED_SELECTOR = YES; 385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 386 | GCC_WARN_UNUSED_FUNCTION = YES; 387 | GCC_WARN_UNUSED_VARIABLE = YES; 388 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 389 | MTL_ENABLE_DEBUG_INFO = NO; 390 | PROVISIONING_PROFILE = ""; 391 | SDKROOT = iphoneos; 392 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 393 | TARGETED_DEVICE_FAMILY = "1,2"; 394 | VALIDATE_PRODUCT = YES; 395 | }; 396 | name = Release; 397 | }; 398 | DFD2202519C643BC00913975 /* Debug */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 402 | CLANG_ENABLE_MODULES = YES; 403 | CODE_SIGN_ENTITLEMENTS = AccessibleVideo/AccessibleVideo.entitlements; 404 | CODE_SIGN_IDENTITY = "iPhone Developer"; 405 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 406 | DEVELOPMENT_TEAM = G8X4XMPVS9; 407 | INFOPLIST_FILE = AccessibleVideo/Info.plist; 408 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 409 | OTHER_SWIFT_FLAGS = ""; 410 | PRODUCT_BUNDLE_IDENTIFIER = "com.doomsdaytech.$(PRODUCT_NAME:rfc1034identifier)"; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | PROVISIONING_PROFILE = "844e4de1-31f3-41ed-a714-ff03741587a1"; 413 | PROVISIONING_PROFILE_SPECIFIER = "Test Development"; 414 | SWIFT_OBJC_BRIDGING_HEADER = "AccessibleVideo-Bridging-Header.h"; 415 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 416 | SWIFT_VERSION = 3.0; 417 | }; 418 | name = Debug; 419 | }; 420 | DFD2202619C643BC00913975 /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 424 | CLANG_ENABLE_MODULES = YES; 425 | CODE_SIGN_ENTITLEMENTS = AccessibleVideo/AccessibleVideo.entitlements; 426 | CODE_SIGN_IDENTITY = "iPhone Developer"; 427 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 428 | DEVELOPMENT_TEAM = G8X4XMPVS9; 429 | INFOPLIST_FILE = AccessibleVideo/Info.plist; 430 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 431 | OTHER_SWIFT_FLAGS = ""; 432 | PRODUCT_BUNDLE_IDENTIFIER = "com.doomsdaytech.$(PRODUCT_NAME:rfc1034identifier)"; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | PROVISIONING_PROFILE = "844e4de1-31f3-41ed-a714-ff03741587a1"; 435 | PROVISIONING_PROFILE_SPECIFIER = "Test Development"; 436 | SWIFT_OBJC_BRIDGING_HEADER = "AccessibleVideo-Bridging-Header.h"; 437 | SWIFT_VERSION = 3.0; 438 | }; 439 | name = Release; 440 | }; 441 | /* End XCBuildConfiguration section */ 442 | 443 | /* Begin XCConfigurationList section */ 444 | DFD2200019C643BC00913975 /* Build configuration list for PBXProject "AccessibleVideo" */ = { 445 | isa = XCConfigurationList; 446 | buildConfigurations = ( 447 | DFD2202219C643BC00913975 /* Debug */, 448 | DFD2202319C643BC00913975 /* Release */, 449 | ); 450 | defaultConfigurationIsVisible = 0; 451 | defaultConfigurationName = Release; 452 | }; 453 | DFD2202419C643BC00913975 /* Build configuration list for PBXNativeTarget "AccessibleVideo" */ = { 454 | isa = XCConfigurationList; 455 | buildConfigurations = ( 456 | DFD2202519C643BC00913975 /* Debug */, 457 | DFD2202619C643BC00913975 /* Release */, 458 | ); 459 | defaultConfigurationIsVisible = 0; 460 | defaultConfigurationName = Release; 461 | }; 462 | /* End XCConfigurationList section */ 463 | }; 464 | rootObject = DFD21FFD19C643BC00913975 /* Project object */; 465 | } 466 | -------------------------------------------------------------------------------- /AccessibleVideo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AccessibleVideo.xcodeproj/project.xcworkspace/xcshareddata/AccessibleVideo.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "243CB3B018D5C8570178E0E0D9BF07D716D79817", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "243CB3B018D5C8570178E0E0D9BF07D716D79817" : 0, 8 | "4182765C525509B305166AB949D5935FD5C85212" : 0 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "68558891-4502-4CD5-8CA3-CBF431D1662D", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "243CB3B018D5C8570178E0E0D9BF07D716D79817" : "AccessibleVideo", 13 | "4182765C525509B305166AB949D5935FD5C85212" : "AccessibleVideoMBProgressHUD" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "AccessibleVideo", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "AccessibleVideo.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/dghost\/AccessibleVideo.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "243CB3B018D5C8570178E0E0D9BF07D716D79817" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/jdg\/MBProgressHUD.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4182765C525509B305166AB949D5935FD5C85212" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /AccessibleVideo.xcodeproj/xcshareddata/xcschemes/AccessibleVideo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /AccessibleVideo/AccessibleVideo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.ubiquity-kvstore-identifier 6 | $(TeamIdentifierPrefix)$(CFBundleIdentifier) 7 | 8 | 9 | -------------------------------------------------------------------------------- /AccessibleVideo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 9/14/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | NSUbiquitousKeyValueStore.default().synchronize() 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 | -------------------------------------------------------------------------------- /AccessibleVideo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /AccessibleVideo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 214 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 314 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | -------------------------------------------------------------------------------- /AccessibleVideo/BlurBuffer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlurBuffer.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 12/27/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | 10 | class BlurBuffer:MetalBuffer { 11 | 12 | fileprivate var _x:UnsafeMutablePointer! = nil 13 | fileprivate var _y:UnsafeMutablePointer! = nil 14 | 15 | override func setContents(_ arguments: MTLArgument) { 16 | if arguments.name == "blurParameters" { 17 | _x = nil 18 | _y = nil 19 | 20 | let parameters = arguments.bufferStructType.members as [MTLStructMember] 21 | for parameter in parameters { 22 | print("Found parameter \(parameter.name) at offset \(parameter.offset)") 23 | let pointer = _filterBufferData.advanced(by: parameter.offset) 24 | 25 | switch(parameter.name) { 26 | case "xOffsets": 27 | _x = pointer.assumingMemoryBound(to: Float32.self) 28 | break; 29 | case "yOffsets": 30 | _y = pointer.assumingMemoryBound(to: Float32.self) 31 | break; 32 | default: 33 | print("Error: unknown parameter") 34 | break; 35 | } 36 | } 37 | } 38 | } 39 | 40 | var xOffsets:((Float32,Float32),(Float32,Float32),(Float32,Float32)) { 41 | get { 42 | return ((_x![0],_x![1]),(_x![2],_x![3]),(_x![4],_x![5])) 43 | } 44 | set { 45 | _x[0] = newValue.0.0 46 | _x[1] = newValue.0.1 47 | _x[2] = newValue.1.0 48 | _x[3] = newValue.1.1 49 | _x[4] = newValue.2.0 50 | _x[5] = newValue.2.1 51 | } 52 | } 53 | 54 | var yOffsets:((Float32,Float32),(Float32,Float32),(Float32,Float32)) { 55 | get { 56 | return ((_y![0],_y![1]),(_y![2],_y![3]),(_y![4],_y![5])) 57 | } 58 | set { 59 | _y[0] = newValue.0.0 60 | _y[1] = newValue.0.1 61 | _y[2] = newValue.1.0 62 | _y[3] = newValue.1.1 63 | _y[4] = newValue.2.0 64 | _y[5] = newValue.2.1 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /AccessibleVideo/BufferTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BufferTypes.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 12/27/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | 10 | 11 | class MetalBuffer { 12 | fileprivate let buffer:MTLBuffer! 13 | internal let _filterBufferData:UnsafeMutableRawPointer! 14 | internal let _filterBufferSize:Int 15 | 16 | init!(arguments:MTLArgument) { 17 | let size = arguments.bufferDataSize 18 | let dev = MTLCreateSystemDefaultDevice() 19 | 20 | let options:MTLResourceOptions = MTLResourceOptions() 21 | 22 | buffer = dev!.makeBuffer(length: size, options: options) 23 | _filterBufferData = UnsafeMutableRawPointer(buffer!.contents()) 24 | _filterBufferSize = size 25 | setContents(arguments) 26 | } 27 | 28 | required init!(base:UnsafeMutableRawPointer!, size:Int, arguments:MTLArgument) { 29 | buffer = nil 30 | _filterBufferData = base 31 | _filterBufferSize = size 32 | setContents(arguments) 33 | } 34 | 35 | func setContents(_ arguments: MTLArgument) { 36 | assert(false, "This should not be getting called!") 37 | } 38 | } 39 | 40 | class MetalBufferArray { 41 | fileprivate let buffer:MTLBuffer! 42 | internal let _filterBufferData:UnsafeMutableRawPointer 43 | internal let _filterBufferSize:Int 44 | lazy internal var _members = [T]() 45 | 46 | init!(arguments:MTLArgument, count:Int){ 47 | let size = arguments.bufferDataSize 48 | let dev = MTLCreateSystemDefaultDevice() 49 | 50 | let options:MTLResourceOptions = MTLResourceOptions() 51 | 52 | buffer = dev!.makeBuffer(length: size * count, options: options) 53 | _filterBufferData = UnsafeMutableRawPointer(buffer!.contents()) 54 | _filterBufferSize = size 55 | _members = (0.. T { 62 | get { 63 | assert(element >= 0 && element < _members.count , "Index out of range") 64 | return _members[element] 65 | } 66 | } 67 | 68 | func bufferAndOffsetForElement(_ element:Int) -> (MTLBuffer, Int){ 69 | assert(element >= 0 && element < _members.count , "Index out of range") 70 | return (buffer!,_filterBufferSize * element) 71 | } 72 | 73 | func offsetForElement(_ element:Int) -> Int { 74 | assert(element >= 0 && element < _members.count , "Index out of range") 75 | return _filterBufferSize * element 76 | } 77 | 78 | var count:Int { 79 | return _members.count 80 | } 81 | } 82 | 83 | // type takes in a UIColor or CGFloats and writes them out as an 84 | // 8-bit per channel RGBA vector 85 | struct Color { 86 | fileprivate let _base:UnsafeMutablePointer 87 | init(buffer:UnsafeMutablePointer) { 88 | _base = buffer 89 | } 90 | 91 | var color:UIColor { 92 | get { 93 | return UIColor(red: r, green: g, blue: b, alpha: a) 94 | } 95 | set { 96 | newValue.getRed(&r, green: &g, blue: &b, alpha: &a) 97 | } 98 | } 99 | 100 | var inverseColor:UIColor { 101 | get { 102 | return UIColor(red: 1.0 - r, green: 1.0 - g, blue: 1.0 - b, alpha: a) 103 | } 104 | set { 105 | var rt:CGFloat = 0.0, gt:CGFloat = 0.0, bt:CGFloat = 0.0 106 | newValue.getRed(&rt, green: >, blue: &bt, alpha: &self.a) 107 | self.r = 1.0 - rt 108 | self.g = 1.0 - gt 109 | self.b = 1.0 - bt 110 | } 111 | } 112 | 113 | var r:CGFloat { 114 | get { 115 | return CGFloat(Float(_base[0]) / 255.0) 116 | } 117 | set { 118 | let clamped = newValue < 0.0 ? 0.0 : (newValue > 1.0 ? 1.0 : newValue) 119 | _base[0] = UInt8(clamped * 255.0) 120 | } 121 | } 122 | var g:CGFloat { 123 | get { 124 | return CGFloat(Float(_base[1]) / 255.0) 125 | } 126 | set { 127 | let clamped = newValue < 0.0 ? 0.0 : (newValue > 1.0 ? 1.0 : newValue) 128 | _base[1] = UInt8(clamped * 255.0) 129 | } 130 | } 131 | var b:CGFloat { 132 | get { 133 | return CGFloat(Float(_base[2]) / 255.0) 134 | } 135 | set { 136 | let clamped = newValue < 0.0 ? 0.0 : (newValue > 1.0 ? 1.0 : newValue) 137 | _base[2] = UInt8(clamped * 255.0) 138 | } 139 | } 140 | var a:CGFloat { 141 | get { 142 | return CGFloat(Float(_base[3]) / 255.0) 143 | } 144 | set { 145 | let clamped = newValue < 0.0 ? 0.0 : (newValue > 1.0 ? 1.0 : newValue) 146 | _base[3] = UInt8(clamped * 255.0) 147 | } 148 | } 149 | } 150 | 151 | 152 | // type takes in a row-major matrix and writes it so that 153 | // it is a column-major matrix where each column is aligned on 154 | // 4 byte boundaries 155 | struct Matrix3x3 { 156 | fileprivate let _base:UnsafeMutablePointer 157 | init(buffer:UnsafeMutablePointer) { 158 | _base = buffer 159 | } 160 | 161 | fileprivate func indexIsValidForRow(_ row: Int, column: Int) -> Bool { 162 | return row >= 0 && row < 3 && column >= 0 && column < 3 163 | } 164 | 165 | subscript(row:Int, column:Int) -> Float32 { 166 | get { 167 | assert(indexIsValidForRow(row, column: column), "Index out of range") 168 | // convert to column-major order 169 | return _base[(column * 4) + row] 170 | } 171 | set { 172 | assert(indexIsValidForRow(row, column: column), "Index out of range") 173 | // convert to column-major order 174 | _base[(column * 4) + row] = newValue 175 | } 176 | } 177 | 178 | subscript(row:Int) -> (Float32, Float32, Float32) { 179 | get { 180 | assert(row >= 0 && row < 3, "Index out of range") 181 | // convert to column-major order 182 | return (_base[row], _base[row + 4], _base[row + 8]) 183 | } 184 | set { 185 | assert(row >= 0 && row < 3 , "Index out of range") 186 | // convert to column-major order 187 | _base[row] = newValue.0 188 | _base[row + 4] = newValue.1 189 | _base[row + 8] = newValue.2 190 | } 191 | } 192 | 193 | func set(_ matrix:((Float32, Float32, Float32), (Float32, Float32, Float32), (Float32, Float32, Float32))) { 194 | // converts to column-major order 195 | // aligns each column to 4-byte boundaries 196 | _base[0] = matrix.0.0 197 | _base[4] = matrix.0.1 198 | _base[8] = matrix.0.2 199 | _base[1] = matrix.1.0 200 | _base[5] = matrix.1.1 201 | _base[9] = matrix.1.2 202 | _base[2] = matrix.2.0 203 | _base[6] = matrix.2.1 204 | _base[10] = matrix.2.2 205 | } 206 | 207 | func get() -> ((Float32, Float32, Float32), (Float32, Float32, Float32), (Float32, Float32, Float32)) { 208 | return ((_base[0], _base[4], _base[8]), (_base[1], _base[5], _base[9]), (_base[2], _base[6], _base[10])) 209 | } 210 | 211 | func clear() { 212 | for index in 0...15 { 213 | _base[index] = 0.0 214 | } 215 | } 216 | 217 | func clearIdentity() { 218 | for index in 0...15 { 219 | _base[index] = (index % 4 == 0) ? 1.0 : 0.0 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /AccessibleVideo/CameraController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Camera.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 10/4/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | import CoreVideo 12 | 13 | 14 | protocol CameraCaptureDelegate { 15 | func setResolution(width: Int, height: Int) 16 | func captureBuffer(_ sampleBuffer:CMSampleBuffer!) 17 | } 18 | 19 | 20 | func str4 (_ n: Int) -> String 21 | { 22 | var s: String = String (describing: UnicodeScalar((n >> 24) & 255)!) 23 | s.append(String(describing: UnicodeScalar((n >> 16) & 255)!)) 24 | s.append(String(describing: UnicodeScalar((n >> 8) & 255)!)) 25 | s.append(String(describing: UnicodeScalar(n & 255)!)) 26 | return (s) 27 | } 28 | 29 | class CameraController:NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { 30 | var delegate:CameraCaptureDelegate? = nil { 31 | didSet { 32 | if let format = _currentFormat { 33 | let resolution = CMVideoFormatDescriptionGetDimensions(format.formatDescription) 34 | delegate?.setResolution(width: Int(resolution.width), height: Int(resolution.height)) 35 | } 36 | } 37 | } 38 | var supportsFrontCamera:Bool { 39 | get { 40 | return _supportsFrontCamera 41 | } 42 | } 43 | 44 | var supportsTorch:Bool { 45 | get { 46 | return _captureDevice?.isTorchAvailable ?? false 47 | } 48 | } 49 | 50 | var torchMode:Bool { 51 | get { 52 | return (_captureDevice?.torchMode ?? .off) == .on ? true : false 53 | } 54 | set { 55 | if _captureDevice?.isTorchAvailable ?? false { 56 | if let _ = try? _captureDevice?.lockForConfiguration() { 57 | _captureDevice?.torchMode = newValue ? .on : .off 58 | _captureDevice?.unlockForConfiguration() 59 | } 60 | } 61 | } 62 | } 63 | 64 | var useFrontCamera:Bool { 65 | get { 66 | return _preferredDevicePosition == .front 67 | } 68 | set { 69 | _preferredDevicePosition = newValue ? .front : .back 70 | } 71 | } 72 | 73 | var running:Bool = false { 74 | willSet { 75 | if _captureSession.isRunning != newValue { 76 | if newValue == true { 77 | _captureSession.startRunning() 78 | } else { 79 | _captureSession.stopRunning() 80 | } 81 | } 82 | } 83 | } 84 | 85 | fileprivate var _supportsFrontCamera:Bool = false 86 | fileprivate var _currentFormat:AVCaptureDeviceFormat? = nil 87 | fileprivate var _supportedFormats:[AVCaptureDeviceFormat]? = nil 88 | fileprivate var _captureInput:AVCaptureInput? = nil 89 | fileprivate var _captureOutput:AVCaptureVideoDataOutput? = nil 90 | fileprivate var _captureConnection:AVCaptureConnection? = nil 91 | fileprivate let _captureSession = AVCaptureSession() 92 | fileprivate var _captureDevice : AVCaptureDevice? = nil 93 | fileprivate var _captureDevices = [AVCaptureDevicePosition : AVCaptureDevice]() 94 | lazy fileprivate var _cameraQueue: DispatchQueue = DispatchQueue(label: "com.doomsdaytech.cameraqueue", attributes: []) 95 | 96 | fileprivate var _preferredFormat:String = "420v" 97 | fileprivate var _preferredMinFrameRate:Int32 = 60 98 | 99 | fileprivate var _preferredResolution:CGSize = CGSize(width: 1280, height: 720) 100 | 101 | fileprivate var _preferredDevicePosition:AVCaptureDevicePosition = .back { 102 | didSet { 103 | DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.high).async { 104 | if self.setDevice(self._preferredDevicePosition) { 105 | print("Successfully set device position") 106 | } else { 107 | print("Error setting device position") 108 | } 109 | } 110 | } 111 | } 112 | 113 | override init() { 114 | super.init() 115 | setupCamera() 116 | self.setDevice(self._preferredDevicePosition) 117 | } 118 | 119 | func setDevice(_ devicePosition:AVCaptureDevicePosition) -> Bool { 120 | // Loop through all the capture devices on this phone 121 | guard let device = _captureDevices[devicePosition], _captureDevice?.position != devicePosition else { 122 | return false 123 | 124 | } 125 | _captureDevice = device 126 | 127 | let formats = (device.formats as! [AVCaptureDeviceFormat]).filter { 128 | let formatCode = str4(Int(CMFormatDescriptionGetMediaSubType($0.formatDescription))) 129 | let resolution = CMVideoFormatDescriptionGetDimensions($0.formatDescription) 130 | let ranges = ($0.videoSupportedFrameRateRanges as! [AVFrameRateRange]).filter { 131 | $0.maxFrameRate >= Float64(self._preferredMinFrameRate) 132 | } 133 | return (formatCode == self._preferredFormat 134 | && Int32(self._preferredResolution.width) <= resolution.width 135 | && Int32(self._preferredResolution.height) <= resolution.height 136 | && ranges.count > 0) 137 | } 138 | 139 | _supportedFormats = formats 140 | 141 | for format in formats { 142 | _currentFormat = format 143 | guard let _ = try? _captureDevice?.lockForConfiguration() else { 144 | return false; 145 | } 146 | 147 | _captureDevice?.activeFormat = format 148 | // cap the framerate to the max set above 149 | _captureDevice?.activeVideoMaxFrameDuration = CMTimeMake(1, _preferredMinFrameRate) 150 | _captureDevice?.activeVideoMinFrameDuration = CMTimeMake(1, _preferredMinFrameRate) 151 | // _captureDevice?.activeVideoMaxFrameDuration = bestFrameRate.minFrameDuration 152 | // _captureDevice?.activeVideoMinFrameDuration = bestFrameRate.minFrameDuration 153 | _captureDevice?.unlockForConfiguration() 154 | 155 | if self.running { 156 | _captureSession.stopRunning() 157 | } 158 | 159 | _captureSession.beginConfiguration() 160 | 161 | if let connection = _captureConnection { 162 | _captureSession.remove(connection) 163 | } 164 | if let input = _captureInput { 165 | _captureSession.removeInput(input) 166 | } 167 | 168 | do { 169 | _captureInput = try AVCaptureDeviceInput(device: device) 170 | } catch let error as NSError { 171 | _captureInput = nil 172 | print("Failed to create AVCaptureDeviceInput, error \(error)") 173 | return false 174 | } 175 | 176 | _captureConnection = AVCaptureConnection(inputPorts: _captureInput!.ports, output: _captureOutput! as AVCaptureOutput) 177 | 178 | print("Supports video orientation: \(_captureConnection!.isVideoOrientationSupported)") 179 | 180 | if devicePosition == .front { 181 | _captureConnection!.videoOrientation = .landscapeRight 182 | _captureConnection!.automaticallyAdjustsVideoMirroring = false 183 | _captureConnection!.isVideoMirrored = true 184 | } 185 | 186 | print("Video mirrored: \(_captureConnection!.isVideoMirrored)") 187 | 188 | _captureSession.addInputWithNoConnections(_captureInput) 189 | _captureSession.add(_captureConnection) 190 | _captureSession.commitConfiguration() 191 | 192 | _cameraQueue.async { 193 | let resolution = CMVideoFormatDescriptionGetDimensions(format.formatDescription) 194 | self.delegate?.setResolution(width: Int(resolution.width), height: Int(resolution.height)) 195 | } 196 | 197 | 198 | if self.running { 199 | _captureSession.startRunning() 200 | } 201 | 202 | return true 203 | } 204 | return false 205 | } 206 | 207 | 208 | func setupCamera() { 209 | // initialize AVCaptureSession 210 | 211 | let devices = (AVCaptureDevice.devices() as! [AVCaptureDevice]).filter { 212 | ($0.position == .front || $0.position == .back) && $0.hasMediaType(AVMediaTypeVideo) 213 | } 214 | 215 | for device in devices { 216 | var position:String = "Unknown" 217 | 218 | if device.position == .front { 219 | position = "Front Camera" 220 | } else if device.position == .back { 221 | position = "Rear Camera" 222 | } 223 | 224 | print("Device found: \(position)") 225 | print("...supports torch: \(device.isTorchAvailable)") 226 | 227 | let formats = (device.formats as! [AVCaptureDeviceFormat]).filter { 228 | let format = str4(Int(CMFormatDescriptionGetMediaSubType($0.formatDescription))) 229 | return self._preferredFormat == format 230 | } 231 | 232 | for format in formats { 233 | let resolution = CMVideoFormatDescriptionGetDimensions(format.formatDescription) 234 | 235 | let ranges = (format.videoSupportedFrameRateRanges as! [AVFrameRateRange]).filter { 236 | $0.maxFrameRate >= Float64(self._preferredMinFrameRate) 237 | } 238 | if ranges.count > 0 { 239 | _captureDevices[device.position] = device 240 | if device.position == .front { 241 | _supportsFrontCamera = true 242 | } 243 | } 244 | 245 | for range:AVFrameRateRange in ranges { 246 | print("Found format with resolution \(resolution.width)x\(resolution.height)") 247 | print("...supports up to \(range.maxFrameRate)fps video") 248 | print("...supports HDR: \(format.isVideoHDRSupported)") 249 | print("...supports auto stabilization: \(format.isVideoStabilizationModeSupported(.auto))") 250 | print("...supports cinematic stabilization: \(format.isVideoStabilizationModeSupported(.cinematic))") 251 | } 252 | } 253 | 254 | } 255 | 256 | 257 | 258 | _captureOutput = AVCaptureVideoDataOutput() 259 | _captureOutput?.alwaysDiscardsLateVideoFrames = true 260 | 261 | let vidSettings = [kCVPixelBufferPixelFormatTypeKey as NSObject:NSNumber(value: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange as UInt32) ] 262 | _captureOutput?.videoSettings = vidSettings 263 | 264 | _captureOutput?.setSampleBufferDelegate(self, queue: _cameraQueue) 265 | 266 | _captureSession.beginConfiguration() 267 | _captureSession.addOutputWithNoConnections(_captureOutput) 268 | _captureSession.sessionPreset = AVCaptureSessionPresetInputPriority 269 | _captureSession.commitConfiguration() 270 | 271 | } 272 | 273 | func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) { 274 | delegate?.captureBuffer(sampleBuffer) 275 | } 276 | 277 | var supportedResolutions:[CGSize?] { 278 | get { 279 | let formatsAndResolutions = (_captureDevice!.formats as! [AVCaptureDeviceFormat]).map { 280 | return ($0, CMVideoFormatDescriptionGetDimensions($0.formatDescription)) 281 | } 282 | 283 | let filtered = formatsAndResolutions.filter { 284 | let format = $0.0 285 | let resolution = $0.1 286 | let formatCode = str4(Int(CMFormatDescriptionGetMediaSubType(format.formatDescription))) 287 | let ranges = (format.videoSupportedFrameRateRanges as! [AVFrameRateRange]).filter { 288 | $0.maxFrameRate >= Float64(self._preferredMinFrameRate) 289 | } 290 | return (formatCode == self._preferredFormat 291 | && Int32(self._preferredResolution.width) <= resolution.width 292 | && Int32(self._preferredResolution.height) <= resolution.height 293 | && ranges.count > 0) 294 | } 295 | 296 | return filtered.map { 297 | return CGSize(width: CGFloat($0.1.width), height: CGFloat($0.1.height)) 298 | } 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /AccessibleVideo/ColorBuffer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorBuffer.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 12/27/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | 10 | class ColorBuffer:MetalBuffer { 11 | var yuvToRGB:Matrix3x3! = nil 12 | 13 | override func setContents(_ arguments: MTLArgument) { 14 | if arguments.name == "colorParameters" { 15 | yuvToRGB = nil 16 | 17 | let parameters = arguments.bufferStructType.members as [MTLStructMember] 18 | for parameter in parameters { 19 | print("Found parameter \(parameter.name) at offset \(parameter.offset)") 20 | let pointer = _filterBufferData.advanced(by: parameter.offset) 21 | 22 | switch(parameter.name) { 23 | case "yuvToRGB": 24 | yuvToRGB = Matrix3x3(buffer: pointer.assumingMemoryBound(to: Float32.self)) 25 | break; 26 | default: 27 | print("Error: unknown parameter") 28 | break; 29 | } 30 | } 31 | } 32 | } 33 | 34 | func setConvolution(_ newConvolution:[Float32]) { 35 | if newConvolution.count == 9 { 36 | yuvToRGB.set( 37 | ( 38 | (newConvolution[0], newConvolution[1], newConvolution[2]), 39 | (newConvolution[3], newConvolution[4], newConvolution[5]), 40 | (newConvolution[6], newConvolution[7], newConvolution[8]) 41 | ) 42 | ) 43 | } else { 44 | yuvToRGB.clearIdentity() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /AccessibleVideo/FilterBuffer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterBuffer.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 12/21/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | class FilterBuffer:MetalBuffer { 10 | fileprivate var _lowThreshold:UnsafeMutablePointer! = nil 11 | fileprivate var _highThreshold:UnsafeMutablePointer! = nil 12 | 13 | override func setContents(_ arguments: MTLArgument) { 14 | if arguments.name == "filterParameters" { 15 | primaryColor = nil 16 | secondaryColor = nil 17 | _lowThreshold = nil 18 | _highThreshold = nil 19 | 20 | let parameters = arguments.bufferStructType.members as [MTLStructMember] 21 | for parameter in parameters { 22 | print("Found parameter \(parameter.name) at offset \(parameter.offset)") 23 | let pointer = _filterBufferData.advanced(by: parameter.offset) 24 | 25 | switch(parameter.name) { 26 | case "primaryColor": 27 | primaryColor = Color(buffer: pointer.assumingMemoryBound(to: UInt8.self)) 28 | break; 29 | case "secondaryColor": 30 | secondaryColor = Color(buffer: pointer.assumingMemoryBound(to: UInt8.self)) 31 | break; 32 | case "lowThreshold": 33 | _lowThreshold = pointer.assumingMemoryBound(to: Float32.self) 34 | break; 35 | case "highThreshold": 36 | _highThreshold = pointer.assumingMemoryBound(to: Float32.self) 37 | break; 38 | default: 39 | print("Error: unknown parameter") 40 | break; 41 | } 42 | } 43 | } 44 | } 45 | 46 | 47 | var primaryColor:Color! = nil 48 | var secondaryColor:Color! = nil 49 | 50 | var lowThreshold:Float32 { 51 | get { 52 | return _lowThreshold[0] 53 | } 54 | set { 55 | _lowThreshold[0] = newValue 56 | } 57 | } 58 | 59 | var highThreshold:Float32 { 60 | get { 61 | return _highThreshold[0] 62 | } 63 | set { 64 | _highThreshold[0] = newValue 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /AccessibleVideo/FilterModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterModel.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 11/29/15. 6 | // Copyright © 2015 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | class FilterModel { 13 | fileprivate var _dict:NSDictionary! 14 | 15 | let videoFilters:FilterManager! 16 | 17 | let inputFilters:FilterManager! 18 | 19 | 20 | init?(path:String) { 21 | guard let dict = NSDictionary(contentsOfFile: path), 22 | let vidFilters:[[String : Any]] = dict["Video"] as? [[String : Any]], 23 | let inFilters:[[String : Any]] = dict["Input"] as? [[String : Any]] 24 | else { 25 | return nil 26 | } 27 | 28 | self._dict = dict 29 | videoFilters = FilterManager(filters:vidFilters) 30 | inputFilters = FilterManager(filters:inFilters) 31 | } 32 | 33 | subscript (element:String) -> NSArray? { 34 | get { 35 | if let result = _dict[element] as? NSArray { 36 | return result 37 | } 38 | return nil 39 | } 40 | } 41 | 42 | func getVideoFilter(name:String) -> VideoFilter? { 43 | return videoFilters.getFilter(name: name) 44 | } 45 | 46 | func nextVideoFilter() -> VideoFilter? { 47 | return videoFilters.nextFilter() 48 | } 49 | 50 | func prevVideoFilter() -> VideoFilter? { 51 | return videoFilters.prevFilter() 52 | } 53 | 54 | func getInputFilter(name:String) -> InputFilter? { 55 | return inputFilters.getFilter(name: name) 56 | } 57 | 58 | func nextInputFilter() -> InputFilter? { 59 | return inputFilters.nextFilter() 60 | } 61 | 62 | func prevInputFilter() -> InputFilter? { 63 | return inputFilters.prevFilter() 64 | 65 | } 66 | } 67 | 68 | class InputFilter: FilterProtocol { 69 | let name:String 70 | let shaderName:String 71 | let convolution:[Float32] 72 | 73 | required init() 74 | { 75 | name="Invalid Filter" 76 | shaderName = "yuv_rgb" 77 | convolution = [1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0] 78 | } 79 | 80 | required init?(filterDef: [String : Any]) 81 | { 82 | guard let newName = filterDef["Name"] as? String 83 | else { 84 | return nil 85 | } 86 | 87 | name = newName 88 | 89 | shaderName = filterDef["Shader"] as? String ?? "yuv_rgb" 90 | if let param = filterDef["Convolution"] as? [NSNumber], param.count == 9 91 | { 92 | convolution = param.map {Float32($0.floatValue)} 93 | } else { 94 | convolution = [1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0] 95 | } 96 | } 97 | } 98 | 99 | 100 | class VideoFilter: FilterProtocol { 101 | let name:String 102 | let canBlur:Bool 103 | let passes:[String] 104 | 105 | required init() 106 | { 107 | name="Invalid Filter" 108 | canBlur=false 109 | passes=["blit"] 110 | } 111 | 112 | required init?(filterDef: [String : Any]) 113 | { 114 | guard let newName = filterDef["Name"] as? String 115 | else { 116 | return nil 117 | } 118 | name = newName 119 | canBlur = filterDef["CanUseBlur"] as? Bool ?? false 120 | passes = filterDef["Passes"] as? [String] ?? ["blit"] 121 | } 122 | } 123 | 124 | protocol FilterProtocol { 125 | var name:String { get } 126 | init() 127 | init?(filterDef: [String : Any]) 128 | } 129 | 130 | 131 | class FilterManager 132 | { 133 | let _filterArray:[T]! 134 | let _filterMap:[String:T]! 135 | var _currentFilter:Int = 0 136 | 137 | init(filters: [[String : Any]]) 138 | { 139 | var filterArray:[T] = [] 140 | var filterMap:[String:T] = [:] 141 | 142 | filterArray.reserveCapacity(filters.count) 143 | for filter in filters 144 | { 145 | if let newFilter = T(filterDef: filter) 146 | { 147 | print("Loaded definition for filter \(newFilter.name)") 148 | 149 | filterArray.append(newFilter) 150 | filterMap[newFilter.name] = newFilter 151 | } else { 152 | print("Error instantiating malformed filter \(filter["Name"])") 153 | } 154 | } 155 | 156 | if filterArray.count == 0 { 157 | let emptyFilter = T() 158 | filterArray.append(emptyFilter) 159 | filterMap[emptyFilter.name] = emptyFilter 160 | } 161 | 162 | _filterArray = filterArray 163 | _filterMap = filterMap 164 | } 165 | 166 | func getFilter(name:String) -> T? { 167 | if let filter = _filterMap[name] 168 | { 169 | return filter 170 | } 171 | return nil 172 | } 173 | 174 | func nextFilter() -> T? { 175 | guard _filterArray.count > 0 else { 176 | return nil 177 | } 178 | 179 | _currentFilter = (_currentFilter + 1) % _filterArray.count 180 | return _filterArray[_currentFilter] 181 | } 182 | 183 | func prevFilter() -> T? { 184 | guard _filterArray.count > 0 else { 185 | return nil 186 | } 187 | 188 | _currentFilter = (_currentFilter - 1) % _filterArray.count 189 | if _currentFilter < 0 { 190 | _currentFilter += _filterArray.count 191 | } 192 | return _filterArray[_currentFilter] 193 | } 194 | } 195 | 196 | -------------------------------------------------------------------------------- /AccessibleVideo/FilterRenderer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterRenderer.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 10/4/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreVideo 11 | import Metal 12 | import AVFoundation 13 | import UIKit 14 | 15 | protocol RendererControlDelegate { 16 | var primaryColor:UIColor { get set } 17 | var secondaryColor:UIColor { get set } 18 | var invertScreen:Bool { get set } 19 | var applyBlur:Bool { get set } 20 | var highQuality:Bool { get } 21 | } 22 | 23 | class FilterRenderer: MetalViewDelegate, CameraCaptureDelegate, RendererControlDelegate { 24 | 25 | var device:MTLDevice! { 26 | return _device 27 | } 28 | 29 | 30 | var applyBlur:Bool = false 31 | 32 | var highQuality:Bool = false 33 | 34 | fileprivate var _controller:UIViewController! = nil 35 | 36 | lazy fileprivate var _device = MTLCreateSystemDefaultDevice() 37 | lazy fileprivate var _vertexStart = [UIInterfaceOrientation : Int]() 38 | 39 | fileprivate var _vertexBuffer:MTLBuffer! = nil 40 | fileprivate var _filterArgs:MetalBufferArray! = nil 41 | fileprivate var _colorArgs:MetalBufferArray! = nil 42 | fileprivate var _blurArgs:MetalBufferArray! = nil 43 | 44 | fileprivate var _currentFilterBuffer:Int = 0 { 45 | didSet { 46 | _currentFilterBuffer = _currentFilterBuffer % _numberShaderBuffers 47 | } 48 | } 49 | 50 | fileprivate var _currentColorBuffer:Int = 0 { 51 | didSet { 52 | _currentColorBuffer = _currentColorBuffer % _numberShaderBuffers 53 | } 54 | } 55 | 56 | fileprivate var _currentBlurBuffer:Int = 0 { 57 | didSet { 58 | _currentBlurBuffer = _currentBlurBuffer % _numberShaderBuffers 59 | } 60 | } 61 | 62 | fileprivate var _blurPipelineStates = [MTLRenderPipelineState]() 63 | fileprivate var _screenBlitState:MTLRenderPipelineState! = nil 64 | fileprivate var _screenInvertState:MTLRenderPipelineState! = nil 65 | 66 | fileprivate var _commandQueue: MTLCommandQueue! = nil 67 | 68 | fileprivate var _intermediateTextures = [MTLTexture]() 69 | fileprivate var _intermediateRenderPassDescriptor = [MTLRenderPassDescriptor]() 70 | 71 | 72 | fileprivate var _rgbTexture:MTLTexture! = nil 73 | fileprivate var _rgbDescriptor:MTLRenderPassDescriptor! = nil 74 | fileprivate var _blurTexture:MTLTexture! = nil 75 | fileprivate var _blurDescriptor:MTLRenderPassDescriptor! = nil 76 | 77 | 78 | // ping/pong index variable 79 | fileprivate var _currentSourceTexture:Int = 0 { 80 | didSet { 81 | _currentSourceTexture = _currentSourceTexture % 2 82 | } 83 | } 84 | 85 | fileprivate var _currentDestTexture:Int { 86 | return (_currentSourceTexture + 1) % 2 87 | } 88 | 89 | fileprivate var _numberBufferedFrames:Int = 3 90 | fileprivate var _numberShaderBuffers:Int { 91 | return _numberBufferedFrames + 1 92 | } 93 | 94 | fileprivate var _renderSemaphore: DispatchSemaphore! = nil 95 | 96 | fileprivate var _textureCache: CVMetalTextureCache? = nil 97 | 98 | fileprivate var _vertexDesc: MTLVertexDescriptor! = nil 99 | 100 | fileprivate var _shaderLibrary: MTLLibrary! = nil 101 | fileprivate var _shaderDictionary: NSDictionary! = nil 102 | fileprivate var _shaderPipelineStates = [String : MTLRenderPipelineState]() 103 | 104 | fileprivate var _shaderArguments = [String : MTLRenderPipelineReflection]() 105 | 106 | fileprivate var _samplerStates = [MTLSamplerState]() 107 | 108 | fileprivate var _currentVideoFilterUsesBlur = true 109 | fileprivate var _currentVideoFilter = [MTLRenderPipelineState]() 110 | fileprivate var _currentColorFilter:MTLRenderPipelineState! = nil 111 | 112 | lazy fileprivate var _isiPad:Bool = (UIDevice.current.userInterfaceIdiom == .pad) 113 | 114 | fileprivate var _viewport:MTLViewport = MTLViewport() 115 | 116 | init(viewController:UIViewController!) { 117 | _controller = viewController 118 | setupRenderer() 119 | } 120 | 121 | func setupRenderer() 122 | { 123 | // load the shader dictionary 124 | let path = Bundle.main.path(forResource: "Shaders", ofType: "plist") 125 | _shaderDictionary = NSDictionary(contentsOfFile: path!) 126 | 127 | // create the render buffering semaphore 128 | _renderSemaphore = DispatchSemaphore(value: _numberBufferedFrames) 129 | 130 | // create texture caches for CoreVideo 131 | 132 | CVMetalTextureCacheCreate(nil, nil, _device!, nil, &_textureCache) 133 | 134 | // set up the full screen quads 135 | let data:[Float] = 136 | [ // landscape right & passthrough 137 | -1.0, -1.0, 0.0, 1.0, 138 | 1.0, -1.0, 1.0, 1.0, 139 | -1.0, 1.0, 0.0, 0.0, 140 | 1.0, -1.0, 1.0, 1.0, 141 | -1.0, 1.0, 0.0, 0.0, 142 | 1.0, 1.0, 1.0, 0.0, 143 | // landscape left 144 | -1.0, -1.0, 1.0, 0.0, 145 | 1.0, -1.0, 0.0, 0.0, 146 | -1.0, 1.0, 1.0, 1.0, 147 | 1.0, -1.0, 0.0, 0.0, 148 | -1.0, 1.0, 1.0, 1.0, 149 | 1.0, 1.0, 0.0, 1.0, 150 | // portrait 151 | -1.0, -1.0, 1.0, 1.0, 152 | 1.0, -1.0, 1.0, 0.0, 153 | -1.0, 1.0, 0.0, 1.0, 154 | 1.0, -1.0, 1.0, 0.0, 155 | -1.0, 1.0, 0.0, 1.0, 156 | 1.0, 1.0, 0.0, 0.0, 157 | // portrait upside down 158 | -1.0, -1.0, 0.0, 0.0, 159 | 1.0, -1.0, 0.0, 1.0, 160 | -1.0, 1.0, 1.0, 0.0, 161 | 1.0, -1.0, 0.0, 1.0, 162 | -1.0, 1.0, 1.0, 0.0, 163 | 1.0, 1.0, 1.0, 1.0] 164 | 165 | // set up vertex buffer 166 | let dataSize = data.count * MemoryLayout.size(ofValue: data[0]) // 1 167 | 168 | var options:MTLResourceOptions! 169 | 170 | if #available(iOS 9.0, *) { 171 | options = MTLResourceOptions().union(MTLResourceOptions()) 172 | } else { 173 | // Fallback on earlier versions 174 | options = MTLResourceOptions() 175 | } 176 | 177 | _vertexBuffer = _device!.makeBuffer(bytes: data, length: dataSize, options: options) 178 | 179 | // set vertex indicies start for each rotation 180 | _vertexStart[.landscapeRight] = 0 181 | _vertexStart[.landscapeLeft] = 6 182 | _vertexStart[.portrait] = 12 183 | _vertexStart[.portraitUpsideDown] = 18 184 | 185 | // create default shader library 186 | _shaderLibrary = _device!.newDefaultLibrary()! 187 | print("Loading shader library...") 188 | for str in _shaderLibrary.functionNames { 189 | print("Found shader: \(str)") 190 | } 191 | 192 | // create the full screen quad vertex attribute descriptor 193 | let vert = MTLVertexAttributeDescriptor() 194 | vert.format = .float2 195 | vert.bufferIndex = 0 196 | vert.offset = 0 197 | 198 | let tex = MTLVertexAttributeDescriptor() 199 | tex.format = .float2 200 | tex.bufferIndex = 0 201 | tex.offset = 2 * MemoryLayout.size 202 | 203 | let layout = MTLVertexBufferLayoutDescriptor() 204 | layout.stride = 4 * MemoryLayout.size 205 | layout.stepFunction = MTLVertexStepFunction.perVertex 206 | 207 | 208 | _vertexDesc = MTLVertexDescriptor() 209 | 210 | _vertexDesc.layouts[0] = layout 211 | _vertexDesc.attributes[0] = vert 212 | _vertexDesc.attributes[1] = tex 213 | 214 | 215 | // create filter parameter buffer 216 | // create common pipeline states 217 | 218 | _currentColorFilter = cachedPipelineStateFor("yuv_rgb") 219 | 220 | _screenBlitState = cachedPipelineStateFor("blit") 221 | _screenInvertState = cachedPipelineStateFor("invert") 222 | 223 | var fragmentArgs = (_shaderArguments["blit"]!.fragmentArguments!).filter({$0.name == "filterParameters"}) 224 | if fragmentArgs.count == 1 { 225 | _filterArgs = MetalBufferArray(arguments: fragmentArgs[0], count: _numberShaderBuffers) 226 | } 227 | 228 | fragmentArgs = (_shaderArguments["yuv_rgb"]!.fragmentArguments!).filter({$0.name == "colorParameters"}) 229 | if fragmentArgs.count == 1 { 230 | _colorArgs = MetalBufferArray(arguments: fragmentArgs[0], count: _numberShaderBuffers) 231 | } 232 | 233 | if _device!.supportsFeatureSet(.iOS_GPUFamily2_v1) { 234 | print("Using high quality blur...") 235 | highQuality = true 236 | _blurPipelineStates = ["BlurX_HQ", "BlurY_HQ"].map {self.cachedPipelineStateFor($0)!} 237 | let fragmentArgs = (_shaderArguments["BlurX_HQ"]!.fragmentArguments!).filter({$0.name == "blurParameters"}) 238 | if fragmentArgs.count == 1 { 239 | _blurArgs = MetalBufferArray(arguments: fragmentArgs[0], count: _numberShaderBuffers) 240 | } 241 | } else { 242 | highQuality = false 243 | _blurPipelineStates = ["BlurX", "BlurY"].map {self.cachedPipelineStateFor($0)!} 244 | let fragmentArgs = (_shaderArguments["BlurX"]!.fragmentArguments!).filter({$0.name == "blurParameters"}) 245 | if fragmentArgs.count == 1 { 246 | _blurArgs = MetalBufferArray(arguments: fragmentArgs[0], count: _numberShaderBuffers) 247 | } 248 | } 249 | 250 | setFilterBuffer() 251 | 252 | 253 | let nearest = MTLSamplerDescriptor() 254 | nearest.label = "nearest" 255 | 256 | let bilinear = MTLSamplerDescriptor() 257 | bilinear.label = "bilinear" 258 | bilinear.minFilter = .linear 259 | bilinear.magFilter = .linear 260 | _samplerStates = [nearest, bilinear].map {self._device!.makeSamplerState(descriptor: $0)} 261 | 262 | 263 | 264 | // create the command queue 265 | _commandQueue = _device!.makeCommandQueue() 266 | } 267 | 268 | // create a pipeline state descriptor for a vertex/fragment shader combo 269 | func pipelineStateFor(label:String!, fragmentShader:String!, vertexShader: String?) -> (MTLRenderPipelineState?, MTLRenderPipelineReflection?) { 270 | if let fragmentProgram = _shaderLibrary.makeFunction(name: fragmentShader), let vertexProgram = _shaderLibrary.makeFunction(name: vertexShader ?? "defaultVertex") { 271 | let pipelineStateDescriptor = MTLRenderPipelineDescriptor() 272 | pipelineStateDescriptor.label = label 273 | pipelineStateDescriptor.vertexFunction = vertexProgram 274 | pipelineStateDescriptor.fragmentFunction = fragmentProgram 275 | 276 | pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm 277 | 278 | pipelineStateDescriptor.vertexDescriptor = _vertexDesc 279 | 280 | // create the actual pipeline state 281 | var info:MTLRenderPipelineReflection? = nil 282 | 283 | do { 284 | let pipelineState = try _device!.makeRenderPipelineState(descriptor: pipelineStateDescriptor, options: MTLPipelineOption.bufferTypeInfo, reflection: &info) 285 | return (pipelineState, info) 286 | } catch let pipelineError as NSError { 287 | print("Failed to create pipeline state for shaders \(vertexShader):\(fragmentShader) error \(pipelineError)") 288 | } 289 | } 290 | return (nil, nil) 291 | } 292 | 293 | func cachedPipelineStateFor(_ shaderName:String) -> MTLRenderPipelineState? { 294 | guard let pipeline = _shaderPipelineStates[shaderName] else { 295 | 296 | var fragment:String! = shaderName 297 | var vertex:String? = nil 298 | 299 | if let s = _shaderDictionary.object(forKey: shaderName) as? NSDictionary { 300 | vertex = s.object(forKey: "vertex") as? String 301 | if let frag:String = s.object(forKey: "fragment") as? String { 302 | fragment = frag 303 | } 304 | } 305 | 306 | let (state, reflector) = pipelineStateFor(label:shaderName, fragmentShader: fragment, vertexShader: vertex) 307 | if let pipelineState = state 308 | { 309 | _shaderPipelineStates[shaderName] = pipelineState 310 | _shaderArguments[shaderName] = reflector 311 | } else { 312 | print("Fatal error trying to load pipeline state for \(shaderName)") 313 | } 314 | return state 315 | 316 | } 317 | return pipeline 318 | } 319 | 320 | // create generic render pass 321 | func createRenderPass(_ commandBuffer: MTLCommandBuffer!, 322 | pipeline:MTLRenderPipelineState!, 323 | vertexIndex:Int, fragmentBuffers:[(MTLBuffer,Int)], 324 | sourceTextures:[MTLTexture], 325 | descriptor: MTLRenderPassDescriptor!, 326 | viewport:MTLViewport?) { 327 | let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor) 328 | 329 | let name:String = pipeline.label ?? "Unnamed Render Pass" 330 | renderEncoder.pushDebugGroup(name) 331 | renderEncoder.label = name 332 | if let view = viewport { 333 | renderEncoder.setViewport(view) 334 | } 335 | renderEncoder.setRenderPipelineState(pipeline) 336 | 337 | renderEncoder.setVertexBuffer(_vertexBuffer, offset: 0, at: 0) 338 | 339 | for (index,(buffer, offset)) in fragmentBuffers.enumerated() { 340 | renderEncoder.setFragmentBuffer(buffer, offset: offset, at: index) 341 | } 342 | for (index,texture) in sourceTextures.enumerated() { 343 | renderEncoder.setFragmentTexture(texture, at: index) 344 | } 345 | for (index,samplerState) in _samplerStates.enumerated() { 346 | renderEncoder.setFragmentSamplerState(samplerState, at: index) 347 | } 348 | 349 | renderEncoder.drawPrimitives(type: .triangle, vertexStart: vertexIndex, vertexCount: 6, instanceCount: 1) 350 | renderEncoder.popDebugGroup() 351 | renderEncoder.endEncoding() 352 | } 353 | 354 | func render(_ view: MetalView) { 355 | 356 | let currentOrientation:UIInterfaceOrientation = _isiPad ? UIApplication.shared.statusBarOrientation : .portrait 357 | 358 | guard let currentOffset = _vertexStart[currentOrientation], _rgbTexture != nil else { 359 | return 360 | } 361 | 362 | _renderSemaphore.wait(timeout: DispatchTime.distantFuture) 363 | 364 | let commandBuffer = _commandQueue.makeCommandBuffer() 365 | 366 | var sourceTexture:MTLTexture = _rgbTexture 367 | var destDescriptor:MTLRenderPassDescriptor = _intermediateRenderPassDescriptor[_currentDestTexture] 368 | 369 | func swapTextures() { 370 | self._currentSourceTexture += 1 371 | sourceTexture = self._intermediateTextures[self._currentSourceTexture] 372 | destDescriptor = self._intermediateRenderPassDescriptor[self._currentDestTexture] 373 | } 374 | 375 | var blurTex = _rgbTexture 376 | 377 | if applyBlur && _currentVideoFilterUsesBlur, let args = _blurArgs { 378 | let parameters = [args.bufferAndOffsetForElement(_currentBlurBuffer)] 379 | createRenderPass(commandBuffer, 380 | pipeline: _blurPipelineStates[0], 381 | vertexIndex: 0, 382 | fragmentBuffers: parameters, 383 | sourceTextures: [_rgbTexture], 384 | descriptor: _intermediateRenderPassDescriptor[0], 385 | viewport: nil) 386 | 387 | createRenderPass(commandBuffer, 388 | pipeline: _blurPipelineStates[1], 389 | vertexIndex: 0, 390 | fragmentBuffers: parameters, 391 | sourceTextures: [_intermediateTextures[0]], 392 | descriptor: _blurDescriptor, 393 | viewport: nil) 394 | blurTex = _blurTexture 395 | } 396 | 397 | 398 | // apply all render passes in the current filter 399 | let filterParameters = [_filterArgs.bufferAndOffsetForElement(_currentFilterBuffer)] 400 | for (_, filter) in _currentVideoFilter.enumerated() { 401 | createRenderPass(commandBuffer, 402 | pipeline: filter, 403 | vertexIndex: 0, 404 | fragmentBuffers: filterParameters, 405 | sourceTextures: [sourceTexture, blurTex!, _rgbTexture], 406 | descriptor: destDescriptor, 407 | viewport: nil) 408 | 409 | swapTextures() 410 | } 411 | 412 | 413 | if let screenDescriptor = view.renderPassDescriptor { 414 | 415 | createRenderPass(commandBuffer, 416 | pipeline: invertScreen ? _screenInvertState! : _screenBlitState!, 417 | vertexIndex: currentOffset, 418 | fragmentBuffers: filterParameters, 419 | sourceTextures: [sourceTexture, blurTex!, _rgbTexture], 420 | descriptor: screenDescriptor, 421 | viewport: self._viewport) 422 | 423 | swapTextures() 424 | 425 | } 426 | 427 | // commit buffers to GPU 428 | commandBuffer.addCompletedHandler() { 429 | (cmdb:MTLCommandBuffer!) in 430 | self._renderSemaphore.signal() 431 | return 432 | } 433 | 434 | commandBuffer.present(view.currentDrawable!) 435 | commandBuffer.commit() 436 | } 437 | 438 | func resize(_ size: CGSize) { 439 | if _rgbTexture != nil { 440 | let iWidth = Double(_rgbTexture.width) 441 | let iHeight = Double(_rgbTexture.height) 442 | let aspect = iHeight / iWidth 443 | 444 | 445 | if size.width > size.height { 446 | let newHeight = Double(size.width) * aspect 447 | let diff = (Double(size.height) - newHeight) * 0.5 448 | _viewport = MTLViewport(originX: 0.0, originY: diff, width: Double(size.width), height: newHeight, znear: 0.0, zfar: 1.0) 449 | } else { 450 | let newHeight = Double(size.height) * aspect 451 | let diff = (Double(size.width) - newHeight) * 0.5 452 | _viewport = MTLViewport(originX: diff, originY: 0.0, width: newHeight, height: Double(size.height), znear: 0.0, zfar: 1.0) 453 | } 454 | 455 | if _viewport.originX < 0.0 { 456 | _viewport.originX = 0.0 457 | } 458 | if _viewport.originY < 0.0 { 459 | _viewport.originY = 0.0 460 | } 461 | 462 | if _viewport.width > Double(size.width) { 463 | _viewport.width = Double(size.width) 464 | } 465 | 466 | if _viewport.height > Double(size.height) { 467 | _viewport.height = Double(size.height) 468 | } 469 | 470 | } 471 | } 472 | 473 | func setVideoFilter(_ filter:VideoFilter) 474 | { 475 | _currentVideoFilter = filter.passes.map {self.cachedPipelineStateFor($0)!} 476 | _currentVideoFilterUsesBlur = filter.canBlur 477 | } 478 | 479 | func setColorFilter(_ filter:InputFilter) { 480 | 481 | guard let shader = cachedPipelineStateFor(filter.shaderName) else { 482 | print("Fatal error: could not set color filter to \(filter.shaderName)") 483 | return 484 | } 485 | 486 | let nextBuffer = (_currentColorBuffer + 1) % _numberShaderBuffers 487 | 488 | _currentColorFilter = shader 489 | 490 | _colorArgs[nextBuffer].setConvolution(filter.convolution) 491 | _currentColorBuffer += 1 492 | } 493 | 494 | func setResolution(width: Int, height: Int) { 495 | objc_sync_enter(self) 496 | defer { 497 | objc_sync_exit(self) 498 | } 499 | 500 | let scale = UIScreen.main.nativeScale 501 | 502 | var textureWidth = Int(_controller.view.bounds.width * scale) 503 | var textureHeight = Int(_controller.view.bounds.height * scale) 504 | 505 | if (textureHeight > textureWidth) { 506 | let temp = textureHeight 507 | textureHeight = textureWidth 508 | textureWidth = temp 509 | } 510 | 511 | if ((textureHeight > height) || (textureWidth > width)) { 512 | textureHeight = height 513 | textureWidth = width 514 | } 515 | 516 | print("Setting offscreen texure resolution to \(textureWidth)x\(textureHeight)") 517 | 518 | let descriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm, width: textureWidth, height: textureHeight, mipmapped: false) 519 | 520 | if #available(iOS 9.0, *) { 521 | descriptor.resourceOptions = .storageModePrivate 522 | descriptor.storageMode = .private 523 | descriptor.usage = [.renderTarget, .shaderRead] 524 | } 525 | 526 | 527 | _intermediateTextures = [descriptor,descriptor].map { self._device!.makeTexture(descriptor: $0) } 528 | _intermediateRenderPassDescriptor = _intermediateTextures.map { 529 | let renderDescriptor = MTLRenderPassDescriptor() 530 | renderDescriptor.colorAttachments[0].texture = $0 531 | renderDescriptor.colorAttachments[0].loadAction = .dontCare 532 | renderDescriptor.colorAttachments[0].storeAction = .store 533 | return renderDescriptor 534 | } 535 | 536 | _rgbTexture = _device!.makeTexture(descriptor: descriptor) 537 | _rgbDescriptor = MTLRenderPassDescriptor() 538 | _rgbDescriptor.colorAttachments[0].texture = _rgbTexture 539 | _rgbDescriptor.colorAttachments[0].loadAction = .dontCare 540 | _rgbDescriptor.colorAttachments[0].storeAction = .store 541 | 542 | _blurTexture = _device!.makeTexture(descriptor: descriptor) 543 | _blurDescriptor = MTLRenderPassDescriptor() 544 | _blurDescriptor.colorAttachments[0].texture = _blurTexture 545 | _blurDescriptor.colorAttachments[0].loadAction = .dontCare 546 | _blurDescriptor.colorAttachments[0].storeAction = .store 547 | 548 | setBlurBuffer() 549 | } 550 | 551 | 552 | func captureBuffer(_ sampleBuffer: CMSampleBuffer!) { 553 | if _rgbDescriptor != nil, let tc = _textureCache, let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { 554 | 555 | var y_texture: CVMetalTexture? = nil 556 | let y_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0) 557 | let y_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0) 558 | CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, tc, pixelBuffer, nil, MTLPixelFormat.r8Unorm, y_width, y_height, 0, &y_texture) 559 | 560 | var uv_texture: CVMetalTexture? = nil 561 | let uv_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1) 562 | let uv_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1) 563 | CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, tc, pixelBuffer, nil, MTLPixelFormat.rg8Unorm, uv_width, uv_height, 1, &uv_texture) 564 | 565 | let luma = CVMetalTextureGetTexture(y_texture!)! 566 | let chroma = CVMetalTextureGetTexture(uv_texture!)! 567 | 568 | let yuvTextures:[MTLTexture] = [ luma, chroma ] 569 | 570 | let commandBuffer = _commandQueue.makeCommandBuffer() 571 | 572 | // create the YUV->RGB pass 573 | createRenderPass(commandBuffer, 574 | pipeline: _currentColorFilter, 575 | vertexIndex: 0, 576 | fragmentBuffers: [_colorArgs.bufferAndOffsetForElement(_currentColorBuffer)], 577 | sourceTextures: yuvTextures, 578 | descriptor: _rgbDescriptor, 579 | viewport: nil) 580 | 581 | commandBuffer.commit() 582 | 583 | CVMetalTextureCacheFlush(tc, 0) 584 | } 585 | } 586 | 587 | func setBlurBuffer() { 588 | // 589 | // Texel offset generation for linear sampled gaussian blur 590 | // Source: http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ 591 | // 592 | 593 | let nextBuffer = (_currentBlurBuffer + 1) % _numberShaderBuffers 594 | 595 | guard let currentBuffer = _blurArgs?[nextBuffer] else { 596 | return 597 | } 598 | 599 | let offsets:[Float32] = [ 0.0, 1.3846153846, 3.2307692308 ] 600 | 601 | let texelWidth = 1.0 / Float32(_rgbTexture.width) 602 | let texelHeight = 1.0 / Float32(_rgbTexture.height) 603 | 604 | currentBuffer.xOffsets = ( 605 | (offsets[0] * texelWidth, 0), 606 | (offsets[1] * texelWidth, 0), 607 | (offsets[2] * texelWidth, 0) 608 | ) 609 | 610 | currentBuffer.yOffsets = ( 611 | (0, offsets[0] * texelHeight), 612 | (0, offsets[1] * texelHeight), 613 | (0, offsets[2] * texelHeight) 614 | ) 615 | _currentBlurBuffer += 1 616 | } 617 | 618 | func setFilterBuffer() { 619 | let nextBuffer = (_currentFilterBuffer + 1) % _numberShaderBuffers 620 | _currentFilterBuffer += 1 621 | 622 | let currentBuffer = _filterArgs[nextBuffer] 623 | if invertScreen { 624 | currentBuffer.primaryColor?.inverseColor = primaryColor 625 | currentBuffer.secondaryColor?.inverseColor = secondaryColor 626 | } else { 627 | currentBuffer.primaryColor?.color = primaryColor 628 | currentBuffer.secondaryColor?.color = secondaryColor 629 | } 630 | 631 | if highQuality { 632 | currentBuffer.lowThreshold = 0.05 633 | currentBuffer.highThreshold = 0.10 634 | } else { 635 | currentBuffer.lowThreshold = 0.15 636 | currentBuffer.highThreshold = 0.25 637 | } 638 | } 639 | 640 | var primaryColor:UIColor = UIColor(red: 0.0, green: 1.0, blue: 1.0, alpha: 0.75) { 641 | didSet { 642 | setFilterBuffer() 643 | } 644 | } 645 | 646 | var secondaryColor:UIColor = UIColor(red: 1.0, green: 0.0, blue: 1.0, alpha: 0.75){ 647 | didSet { 648 | setFilterBuffer() 649 | } 650 | } 651 | 652 | var invertScreen:Bool = false { 653 | didSet { 654 | setFilterBuffer() 655 | } 656 | } 657 | 658 | } 659 | -------------------------------------------------------------------------------- /AccessibleVideo/Filters.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Input 6 | 7 | 8 | Name 9 | Full Color 10 | Shader 11 | yuv_rgb 12 | Convolution 13 | 14 | 1 15 | 0 16 | 1.57481 17 | 1 18 | -0.18732 19 | -0.46813 20 | 1 21 | 1.8556 22 | 0 23 | 24 | 25 | 26 | Name 27 | Grayscale 28 | Shader 29 | yuv_grayscale 30 | 31 | 32 | Name 33 | Simulated Protanopia 34 | Shader 35 | yuv_rgb 36 | Convolution 37 | 38 | 1 39 | -0.538011 40 | -0.148487 41 | 0.99 42 | -0.0737588 43 | -0.117853 44 | 1 45 | 1.85747 46 | 0.0204294 47 | 48 | 49 | 50 | Name 51 | Simulated Deuteranopia 52 | Shader 53 | yuv_rgb 54 | Convolution 55 | 56 | 1 57 | -0.41321 58 | 0.340115 59 | 1 60 | 0.0602316 61 | 0.268601 62 | 1.01 63 | 1.84998 64 | -0.0455401 65 | 66 | 67 | 68 | Name 69 | Simulated Tritanopia 70 | Shader 71 | yuv_rgb 72 | Convolution 73 | 74 | 1 75 | -0.169053 76 | 1.47607 77 | 1 78 | 0.143294 79 | -0.35237 80 | 1 81 | 0.169166 82 | -0.506443 83 | 84 | 85 | 86 | Video 87 | 88 | 89 | Name 90 | No Filter 91 | CanUseBlur 92 | 93 | Passes 94 | 95 | 96 | 97 | Name 98 | Sobel 99 | CanUseBlur 100 | 101 | Passes 102 | 103 | sobel 104 | 105 | 106 | 107 | Name 108 | Sobel Composite 109 | CanUseBlur 110 | 111 | Passes 112 | 113 | sobel_composite 114 | 115 | 116 | 117 | Name 118 | Canny 119 | CanUseBlur 120 | 121 | Passes 122 | 123 | CannySobelPass 124 | CannyMagnitude 125 | CannyThreshold 126 | 127 | 128 | 129 | Name 130 | Canny Composite 131 | CanUseBlur 132 | 133 | Passes 134 | 135 | CannySobelPass 136 | CannyMagnitude 137 | CannyThresholdComposite 138 | 139 | 140 | 141 | Name 142 | Comic 143 | CanUseBlur 144 | 145 | Passes 146 | 147 | CannySobelPass 148 | CannyMagnitude 149 | CannyComic 150 | 151 | 152 | 153 | Name 154 | Protanopia Correction 155 | CanUseBlur 156 | 157 | Passes 158 | 159 | protanope 160 | 161 | 162 | 163 | Name 164 | Deuteranopia Correction 165 | CanUseBlur 166 | 167 | Passes 168 | 169 | deuteranope 170 | 171 | 172 | 173 | Name 174 | Tritanopia Correction 175 | CanUseBlur 176 | 177 | Passes 178 | 179 | tritanope 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "idiom" : "iphone", 41 | "size" : "60x60", 42 | "scale" : "3x" 43 | }, 44 | { 45 | "idiom" : "ipad", 46 | "size" : "20x20", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "2x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "29x29", 57 | "scale" : "1x" 58 | }, 59 | { 60 | "idiom" : "ipad", 61 | "size" : "29x29", 62 | "scale" : "2x" 63 | }, 64 | { 65 | "idiom" : "ipad", 66 | "size" : "40x40", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "40x40", 72 | "scale" : "2x" 73 | }, 74 | { 75 | "size" : "76x76", 76 | "idiom" : "ipad", 77 | "filename" : "Icon-76.png", 78 | "scale" : "1x" 79 | }, 80 | { 81 | "size" : "76x76", 82 | "idiom" : "ipad", 83 | "filename" : "Icon-76@2x.png", 84 | "scale" : "2x" 85 | }, 86 | { 87 | "idiom" : "ipad", 88 | "size" : "83.5x83.5", 89 | "scale" : "2x" 90 | } 91 | ], 92 | "info" : { 93 | "version" : 1, 94 | "author" : "xcode" 95 | } 96 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Light-Shadow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "LightShadow.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "LightShadow@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "LightShadow@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Light-Shadow.imageset/LightShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Light-Shadow.imageset/LightShadow.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Light-Shadow.imageset/LightShadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Light-Shadow.imageset/LightShadow@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Light-Shadow.imageset/LightShadow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Light-Shadow.imageset/LightShadow@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Light.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Light.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Light@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Light@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode", 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Light.imageset/Light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Light.imageset/Light.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Light.imageset/Light@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Light.imageset/Light@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Light.imageset/Light@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Light.imageset/Light@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Settings-Shadow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "SettingsShadow.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "SettingsShadow@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "SettingsShadow@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Settings-Shadow.imageset/SettingsShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Settings-Shadow.imageset/SettingsShadow.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Settings-Shadow.imageset/SettingsShadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Settings-Shadow.imageset/SettingsShadow@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Settings-Shadow.imageset/SettingsShadow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Settings-Shadow.imageset/SettingsShadow@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Settings.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Settings@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Settings@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode", 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Settings.imageset/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Settings.imageset/Settings.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Settings.imageset/Settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Settings.imageset/Settings@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Settings.imageset/Settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Settings.imageset/Settings@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Switch-Shadow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "SwitchShadow.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "SwitchShadow@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "SwitchShadow@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Switch-Shadow.imageset/SwitchShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Switch-Shadow.imageset/SwitchShadow.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Switch-Shadow.imageset/SwitchShadow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Switch-Shadow.imageset/SwitchShadow@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Switch-Shadow.imageset/SwitchShadow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Switch-Shadow.imageset/SwitchShadow@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Switch.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Switch.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Switch@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Switch@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode", 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Switch.imageset/Switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Switch.imageset/Switch.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Switch.imageset/Switch@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Switch.imageset/Switch@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/Buttons/Switch.imageset/Switch@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/Buttons/Switch.imageset/Switch@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/ColorFilter.imageset/Brush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/ColorFilter.imageset/Brush.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/ColorFilter.imageset/Brush@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/ColorFilter.imageset/Brush@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/ColorFilter.imageset/Brush@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/ColorFilter.imageset/Brush@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/ColorFilter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Brush.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Brush@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Brush@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/Lock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Lock.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Lock@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Lock@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/Lock.imageset/Lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/Lock.imageset/Lock.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/Lock.imageset/Lock@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/Lock.imageset/Lock@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/Lock.imageset/Lock@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/Lock.imageset/Lock@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/Unlock.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Unlock.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Unlock@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Unlock@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/Unlock.imageset/Unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/Unlock.imageset/Unlock.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/Unlock.imageset/Unlock@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/Unlock.imageset/Unlock@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/Unlock.imageset/Unlock@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/Unlock.imageset/Unlock@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/VideoFilter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "TV.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "TV@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "TV@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/VideoFilter.imageset/TV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/VideoFilter.imageset/TV.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/VideoFilter.imageset/TV@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/VideoFilter.imageset/TV@2x.png -------------------------------------------------------------------------------- /AccessibleVideo/Images.xcassets/HUD/VideoFilter.imageset/TV@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dghost/AccessibleVideo/edfe45b51af15e1e4055942ff85a2c75720b3120/AccessibleVideo/Images.xcassets/HUD/VideoFilter.imageset/TV@3x.png -------------------------------------------------------------------------------- /AccessibleVideo/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 | LSApplicationCategoryType 24 | 25 | LSRequiresIPhoneOS 26 | 27 | NSCameraUsageDescription 28 | This application accesses the camera 29 | UIInterfaceOrientation 30 | UIInterfaceOrientationPortrait 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UIRequiredDeviceCapabilities 36 | 37 | armv7 38 | 39 | UIStatusBarHidden 40 | 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | 45 | UISupportedInterfaceOrientations~ipad 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationPortraitUpsideDown 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /AccessibleVideo/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 9/14/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Metal 11 | import QuartzCore 12 | import AVFoundation 13 | import CoreVideo 14 | 15 | protocol ControlsDelegate { 16 | var blur:Bool { get set } 17 | var lock:Bool { get set } 18 | var invert:Bool { get set } 19 | var frontCamera:Bool { get set } 20 | var autoHideUI:Bool { get set } 21 | var videoFilter:String { get set } 22 | var colorFilter:String { get set } 23 | } 24 | 25 | class MainViewController: UIViewController, UIGestureRecognizerDelegate, UIPopoverPresentationControllerDelegate, MBProgressHUDDelegate, ControlsDelegate { 26 | 27 | // MARK: Public properties 28 | 29 | @IBOutlet weak var _switchView: UIView! 30 | @IBOutlet weak var _settingsView: UIView! 31 | @IBOutlet weak var _hudView: UIView! 32 | 33 | @IBOutlet var tapGesture: UITapGestureRecognizer! 34 | @IBOutlet var longPressGesture: UILongPressGestureRecognizer! 35 | 36 | var renderer:FilterRenderer! = nil 37 | lazy var camera = CameraController() 38 | 39 | var renderview: MetalView! = nil 40 | 41 | fileprivate var _filters = FilterModel(path:Bundle.main.path(forResource: "Filters", ofType: "plist")!)! 42 | 43 | 44 | var _swipeActions = Dictionary ()> () 45 | 46 | // MARK: Private properties 47 | 48 | fileprivate var _timer: CADisplayLink? = nil 49 | 50 | lazy fileprivate var _hud: MBProgressHUD! = MBProgressHUD() 51 | 52 | fileprivate var _lockedImage = UIImageView(image: UIImage(named: "Lock")) 53 | fileprivate var _unlockedImage = UIImageView(image: UIImage(named: "Unlock")) 54 | fileprivate var _filterImage = UIImageView(image: UIImage(named: "VideoFilter")) 55 | fileprivate var _colorImage = UIImageView(image: UIImage(named: "ColorFilter")) 56 | 57 | lazy fileprivate var _defaults = NSUbiquitousKeyValueStore.default() 58 | fileprivate var _defaultsTimer:Timer? = nil 59 | 60 | fileprivate var _uiTimer:Timer? = nil 61 | 62 | fileprivate var _settingsDelegate:SettingsViewDelegate? = nil 63 | 64 | fileprivate var _currentVideoFilter:VideoFilter = VideoFilter() 65 | 66 | fileprivate var _currentColorFilter:InputFilter = InputFilter() 67 | 68 | fileprivate var _counterRotation:CGAffineTransform = CGAffineTransform.identity 69 | 70 | lazy fileprivate var _isiPad:Bool = (UIDevice.current.userInterfaceIdiom == .pad) 71 | 72 | fileprivate var _buttonEnabledColor = UIColor(red: 0.4, green: 1.0, blue: 1.0, alpha: 1.0) 73 | 74 | // MARK: Constructors / Deconstructors 75 | 76 | deinit { 77 | writeDefaults() 78 | NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) 79 | NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) 80 | NotificationCenter.default.removeObserver(self, name: UserDefaults.didChangeNotification, object:nil) 81 | } 82 | 83 | // MARK: UIViewController overrides 84 | 85 | override func viewDidLoad() { 86 | 87 | super.viewDidLoad() 88 | 89 | let scale = UIScreen.main.nativeScale 90 | 91 | _switchView.tintColor = UIColor.white 92 | _switchView.layer.shouldRasterize = true 93 | _switchView.layer.rasterizationScale = scale 94 | _switchView.isHidden = true 95 | 96 | _settingsView.tintColor = UIColor.white 97 | _settingsView.layer.shouldRasterize = true 98 | _settingsView.layer.rasterizationScale = scale 99 | _settingsView.isHidden = true 100 | 101 | _hud.margin = 10.0 102 | _hud.delegate = self 103 | _hudView.addSubview(_hud) 104 | 105 | tapGesture.require(toFail: longPressGesture) 106 | 107 | NotificationCenter.default.addObserver(self, selector: #selector(MainViewController.willEnterForeground(_:)), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) 108 | NotificationCenter.default.addObserver(self, selector: #selector(MainViewController.didEnterBackground(_:)), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil) 109 | NotificationCenter.default.addObserver(self, selector: #selector(MainViewController.externalUpdate(_:)), name: 110 | NSUbiquitousKeyValueStore.didChangeExternallyNotification 111 | , object: nil) 112 | 113 | renderer = FilterRenderer(viewController: self) 114 | 115 | // cast view as MetalView type 116 | renderview = view as! MetalView 117 | 118 | // set up the renderer and set the view delegate 119 | renderview.delegate = renderer 120 | 121 | // set up the camera controller and set the delegate 122 | camera.delegate = renderer 123 | 124 | loadDefaults() 125 | 126 | startRenderLoop() 127 | } 128 | 129 | override func didReceiveMemoryWarning() { 130 | super.didReceiveMemoryWarning() 131 | // Dispose of any resources that can be recreated. 132 | } 133 | 134 | override func viewWillAppear(_ animated: Bool) { 135 | enableVideo(true) 136 | setSwipeFunctions(UIApplication.shared.statusBarOrientation) 137 | } 138 | 139 | override func viewDidDisappear(_ animated: Bool) { 140 | enableVideo(false) 141 | } 142 | 143 | func willEnterForeground(_ sender : AnyObject) { 144 | enableVideo(true) 145 | _defaults.synchronize() 146 | loadDefaults() 147 | if lock { 148 | hideUI() 149 | } else { 150 | showUI() 151 | } 152 | } 153 | 154 | func didEnterBackground(_ sender : AnyObject) { 155 | writeDefaults() 156 | enableVideo(false) 157 | } 158 | 159 | func endRotation() { 160 | objc_sync_enter(self) 161 | defer { 162 | objc_sync_exit(self) 163 | } 164 | if _isRotating { 165 | setSwipeFunctions(UIApplication.shared.statusBarOrientation) 166 | UIView.setAnimationsEnabled(true) 167 | 168 | UIView.animate(withDuration: 0.5, animations: { 169 | () -> Void in 170 | self._settingsView.transform = self._counterRotation 171 | self._switchView.transform = self._counterRotation 172 | self._hudView.transform = self._counterRotation 173 | }) 174 | 175 | _isRotating = false 176 | } 177 | } 178 | 179 | fileprivate var _isRotating = false 180 | func startRotation(_ coordinator:UIViewControllerTransitionCoordinator) { 181 | objc_sync_enter(self) 182 | defer { 183 | objc_sync_exit(self) 184 | } 185 | if !_isRotating { 186 | _isRotating = true 187 | 188 | if _settingsDelegate == nil { 189 | UIView.setAnimationsEnabled(false) 190 | } 191 | 192 | let transform = coordinator.targetTransform 193 | 194 | let invertedRotation = transform.inverted() 195 | 196 | 197 | let currentBounds = self.view.bounds 198 | let settingsFrame = self._settingsView.frame 199 | let switchFrame = self._switchView.frame 200 | let hudFrame = self._hudView.frame 201 | self._counterRotation = self._counterRotation.concatenating(transform) 202 | 203 | coordinator.animate( 204 | alongsideTransition: { 205 | (_) -> Void in 206 | self.view.transform = self.view.transform.concatenating(invertedRotation) 207 | self.view.bounds = currentBounds 208 | self._settingsView.frame = settingsFrame 209 | self._switchView.frame = switchFrame 210 | self._hudView.frame = hudFrame 211 | }, 212 | completion: { 213 | (_) -> Void in 214 | self.endRotation() 215 | } 216 | ) 217 | } 218 | } 219 | 220 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 221 | if !_isiPad { 222 | self.startRotation(coordinator) 223 | } 224 | super.viewWillTransition(to: size, with: coordinator) 225 | } 226 | 227 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 228 | return true 229 | } 230 | 231 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 232 | 233 | if touch.view!.superview == _switchView || touch.view!.superview == _settingsView { 234 | return false 235 | } 236 | return true 237 | } 238 | 239 | @IBAction func handleSettingsButton(_ sender: UIButton) { 240 | if renderer.highQuality { 241 | self.performSegue(withIdentifier: "settings-hq", sender: self) 242 | } else { 243 | self.performSegue(withIdentifier: "settings", sender: self) 244 | } 245 | } 246 | 247 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 248 | if segue.identifier == "settings" || segue.identifier == "settings-hq" { 249 | let nav = segue.destination as! UINavigationController 250 | let popover = nav.viewControllers.first as! SettingsViewController 251 | 252 | // set the navigation controller to be it's own popover presentation controller delegate 253 | nav.popoverPresentationController?.delegate = self 254 | 255 | 256 | // set delegates for settings communication 257 | popover.delegate = self 258 | _settingsDelegate = popover 259 | 260 | // kill the UI dismissal timer 261 | _uiTimer?.invalidate() 262 | enableUI = true 263 | UIView.animate(withDuration: 0.5, animations: { 264 | () -> Void in 265 | self._settingsView.tintColor = self._buttonEnabledColor 266 | }) 267 | } 268 | } 269 | 270 | func setSwipeFunctions(_ orientation:UIInterfaceOrientation) { 271 | if (_isiPad) { 272 | _swipeActions[UISwipeGestureRecognizerDirection.left.rawValue] = nextVideoFilter 273 | _swipeActions[UISwipeGestureRecognizerDirection.right.rawValue] = prevVideoFilter 274 | _swipeActions[UISwipeGestureRecognizerDirection.down.rawValue] = nextColorFilter 275 | _swipeActions[UISwipeGestureRecognizerDirection.up.rawValue] = prevColorFilter 276 | } else { 277 | switch(orientation) { 278 | case .portrait: 279 | _swipeActions[UISwipeGestureRecognizerDirection.left.rawValue] = nextVideoFilter 280 | _swipeActions[UISwipeGestureRecognizerDirection.right.rawValue] = prevVideoFilter 281 | _swipeActions[UISwipeGestureRecognizerDirection.up.rawValue] = nextColorFilter 282 | _swipeActions[UISwipeGestureRecognizerDirection.down.rawValue] = prevColorFilter 283 | break; 284 | case .portraitUpsideDown: 285 | _swipeActions[UISwipeGestureRecognizerDirection.right.rawValue] = nextVideoFilter 286 | _swipeActions[UISwipeGestureRecognizerDirection.left.rawValue] = prevVideoFilter 287 | _swipeActions[UISwipeGestureRecognizerDirection.down.rawValue] = nextColorFilter 288 | _swipeActions[UISwipeGestureRecognizerDirection.up.rawValue] = prevColorFilter 289 | break; 290 | case .landscapeLeft: 291 | _swipeActions[UISwipeGestureRecognizerDirection.up.rawValue] = nextVideoFilter 292 | _swipeActions[UISwipeGestureRecognizerDirection.down.rawValue] = prevVideoFilter 293 | _swipeActions[UISwipeGestureRecognizerDirection.left.rawValue] = nextColorFilter 294 | _swipeActions[UISwipeGestureRecognizerDirection.right.rawValue] = prevColorFilter 295 | break; 296 | case .landscapeRight: 297 | _swipeActions[UISwipeGestureRecognizerDirection.up.rawValue] = nextVideoFilter 298 | _swipeActions[UISwipeGestureRecognizerDirection.down.rawValue] = prevVideoFilter 299 | _swipeActions[UISwipeGestureRecognizerDirection.right.rawValue] = nextColorFilter 300 | _swipeActions[UISwipeGestureRecognizerDirection.left.rawValue] = prevColorFilter 301 | break; 302 | default: 303 | break; 304 | } 305 | } 306 | } 307 | 308 | @IBAction func unwindSegue (_ segue:UIStoryboardSegue){ 309 | if (!_isiPad) { 310 | _settingsDelegate = nil 311 | UIView.animate(withDuration: 0.5, animations: { 312 | () -> Void in 313 | self._settingsView.tintColor = UIColor.white 314 | }) 315 | saveDefaults() 316 | if lock { 317 | hideUI() 318 | } else { 319 | showUI() 320 | } 321 | } 322 | } 323 | 324 | // MARK: Render Loop 325 | 326 | func startRenderLoop() { 327 | _timer = CADisplayLink(target: self, selector: #selector(MainViewController.render)) 328 | _timer?.frameInterval = 1 329 | _timer?.add(to: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode) 330 | enableVideo(true) 331 | 332 | } 333 | 334 | func stopRenderLoop() { 335 | enableVideo(false) 336 | _timer?.invalidate() 337 | _timer = nil 338 | } 339 | 340 | func enableVideo(_ enable:Bool) { 341 | print("Video processing: \(enable)") 342 | 343 | camera.running = enable 344 | UIApplication.shared.isIdleTimerDisabled = enable 345 | 346 | _timer?.isPaused = !enable 347 | 348 | } 349 | 350 | func render() { 351 | autoreleasepool { 352 | self.renderview.display() 353 | } 354 | } 355 | 356 | // MARK: iCloud updating 357 | 358 | func saveDefaults() { 359 | // coalesce writes to the iCloud key/value store to only occure once every 2 seconds at max 360 | _defaultsTimer?.invalidate() 361 | _defaultsTimer = Timer(timeInterval: TimeInterval(2.0), target: self, selector: #selector(MainViewController.writeDefaults), userInfo: nil, repeats: false) 362 | RunLoop.current.add(_defaultsTimer!, forMode: RunLoopMode.defaultRunLoopMode) 363 | } 364 | 365 | func writeDefaults() { 366 | _defaultsTimer = nil 367 | 368 | var changed:Bool = false 369 | 370 | // synchronize values in the key value store 371 | if _defaults.bool(forKey: "lock") != lock { 372 | _defaults.set(lock, forKey: "lock") 373 | changed = true 374 | } 375 | 376 | if _defaults.bool(forKey: "autoHideUI") != autoHideUI { 377 | _defaults.set(autoHideUI, forKey: "autoHideUI") 378 | changed = true 379 | } 380 | 381 | if _defaults.bool(forKey: "blur") != blur { 382 | _defaults.set(blur, forKey: "blur") 383 | changed = true 384 | } 385 | 386 | if _defaults.bool(forKey: "invert") != invert { 387 | _defaults.set(invert, forKey: "invert") 388 | changed = true 389 | } 390 | 391 | if camera.supportsFrontCamera { 392 | if _defaults.bool(forKey: "useFrontCamera") != frontCamera { 393 | _defaults.set(frontCamera, forKey: "useFrontCamera") 394 | changed = true 395 | } 396 | } 397 | 398 | if _defaults.string(forKey: "videoFilter") != videoFilter { 399 | _defaults.set(videoFilter, forKey: "videoFilter") 400 | changed = true 401 | } 402 | 403 | if _defaults.string(forKey: "colorFilter") != colorFilter { 404 | _defaults.set(colorFilter, forKey: "colorFilter") 405 | changed = true 406 | } 407 | 408 | // if something changed, force a synchronization of the key value store 409 | // in reality, this is probably a bad thing to be doing frequently and is likely to get the app throttled 410 | if changed == true { 411 | print("Writing to key-value store...") 412 | _defaults.synchronize() 413 | } 414 | } 415 | 416 | func loadDefaults() { 417 | // load the defaults from the key value store 418 | autoHideUI = _defaults.bool(forKey: "autoHideUI") 419 | lock = _defaults.bool(forKey: "lock") 420 | blur = _defaults.bool(forKey: "blur") 421 | invert = _defaults.bool(forKey: "invert") 422 | frontCamera = camera.supportsFrontCamera ? _defaults.bool(forKey: "useFrontCamera") : false 423 | colorFilter = _defaults.string(forKey: "colorFilter") ?? "" 424 | videoFilter = _defaults.string(forKey: "videoFilter") ?? "" 425 | } 426 | 427 | func externalUpdate(_ notification:Notification) { 428 | 429 | 430 | // read in the updates 431 | guard let keys:NSArray = notification.userInfo?[NSUbiquitousKeyValueStoreChangedKeysKey] as? NSArray else { 432 | return 433 | } 434 | 435 | // stop observing iCloud updates while we update 436 | NotificationCenter.default.removeObserver(self, name: UserDefaults.didChangeNotification, object:nil) 437 | 438 | print("Processing updates...") 439 | for key in keys as! [String] { 440 | print("Received update for \(key)") 441 | switch key { 442 | case "lock": 443 | lock = _defaults.bool(forKey: key) 444 | break; 445 | case "autoHideUI": 446 | autoHideUI = _defaults.bool(forKey: key) 447 | break; 448 | case "blur": 449 | blur = _defaults.bool(forKey: key) 450 | break; 451 | case "invert": 452 | invert = _defaults.bool(forKey: key) 453 | break; 454 | case "useFrontCamera": 455 | if camera.supportsFrontCamera { 456 | frontCamera = _defaults.bool(forKey: key) 457 | } 458 | case "videoFilter": 459 | videoFilter = _defaults.string(forKey: key) ?? "" 460 | break; 461 | case "colorFilter": 462 | colorFilter = _defaults.string(forKey: key) ?? "" 463 | break; 464 | default: 465 | print("Unrecognized key \(key)") 466 | break; 467 | } 468 | } 469 | 470 | // start observing iCloud updates again 471 | NotificationCenter.default.addObserver(self, selector: #selector(MainViewController.externalUpdate(_:)), name: UserDefaults.didChangeNotification, object: nil) 472 | } 473 | 474 | // MARK: UI / Overlay Manipulation 475 | 476 | func showOverlayWithText(_ text:String) { 477 | _hud.hide(animated: false) 478 | _hud.customView = nil 479 | _hud.mode = .text 480 | _hud.label.text = text 481 | _hud.show(animated: true) 482 | _hud.hide(animated: true, afterDelay: 2) 483 | 484 | } 485 | 486 | func showOverlayWithText(_ text:String, andImageView imageView:UIImageView) { 487 | _hud.hide(animated: false) 488 | _hud.customView = imageView 489 | _hud.mode = .customView 490 | _hud.label.text = text 491 | _hud.show(animated: true) 492 | _hud.hide(animated: true, afterDelay: 2) 493 | } 494 | 495 | func showLockOverlay(_ locked:Bool) { 496 | if locked { 497 | showOverlayWithText("Locked", andImageView: _lockedImage) 498 | } else { 499 | showOverlayWithText("Unlocked", andImageView: _unlockedImage) 500 | } 501 | } 502 | 503 | func showUI() { 504 | enableUI = true 505 | 506 | if (autoHideUI) { 507 | _uiTimer = Timer(timeInterval: TimeInterval(2.0), target: self, selector: #selector(MainViewController.hideUI), userInfo: nil, repeats: false) 508 | RunLoop.current.add(_uiTimer!, forMode: RunLoopMode.defaultRunLoopMode) 509 | } 510 | } 511 | 512 | func hideUI() { 513 | enableUI = false 514 | } 515 | 516 | var enableUI:Bool = false { 517 | willSet { 518 | _uiTimer?.invalidate() 519 | _uiTimer = nil 520 | if enableUI != newValue { 521 | if _settingsView.isHidden == newValue { 522 | _settingsView.layer.shouldRasterize = false 523 | UIView.transition(with: _settingsView, duration: 0.25, options: UIViewAnimationOptions.transitionCrossDissolve, 524 | animations: { 525 | () -> Void in 526 | self._settingsView.isHidden = !newValue 527 | }, 528 | completion: { 529 | (finished:Bool) -> Void in 530 | if finished { self._settingsView.layer.shouldRasterize = true } 531 | } 532 | ) 533 | } 534 | 535 | let cameraEnable = camera.supportsFrontCamera ? newValue : false 536 | 537 | if cameraEnable == _switchView.isHidden { 538 | _switchView.layer.shouldRasterize = false 539 | 540 | UIView.transition(with: _switchView, duration: 0.25, options: UIViewAnimationOptions.transitionCrossDissolve, 541 | animations: { 542 | () -> Void in 543 | self._switchView.isHidden = !cameraEnable 544 | }, 545 | completion: { 546 | (finished:Bool) -> Void in 547 | if finished { self._switchView.layer.shouldRasterize = true } 548 | } 549 | ) 550 | 551 | } 552 | } 553 | } 554 | } 555 | 556 | // MARK: Filter Manipulation 557 | func setVideoFilter(_ filter:VideoFilter) 558 | { 559 | if (_currentVideoFilter.name != filter.name) 560 | { 561 | _currentVideoFilter = filter 562 | renderer.setVideoFilter(filter) 563 | showOverlayWithText(filter.name, andImageView: _filterImage) 564 | saveDefaults() 565 | } 566 | } 567 | 568 | func nextVideoFilter() { 569 | if let filter = _filters.videoFilters.nextFilter() 570 | { 571 | setVideoFilter(filter) 572 | } 573 | } 574 | 575 | func prevVideoFilter() { 576 | if let filter = _filters.videoFilters.prevFilter() 577 | { 578 | setVideoFilter(filter) 579 | } 580 | } 581 | 582 | func setColorFilter(_ newFilter:InputFilter) 583 | { 584 | if (_currentColorFilter.name != newFilter.name) 585 | { 586 | _currentColorFilter = newFilter 587 | renderer.setColorFilter(newFilter) 588 | 589 | showOverlayWithText(newFilter.name, andImageView: _colorImage) 590 | saveDefaults() 591 | } 592 | } 593 | func nextColorFilter() { 594 | if let colorFilter = _filters.inputFilters.nextFilter() { 595 | setColorFilter(colorFilter) 596 | } 597 | } 598 | 599 | func prevColorFilter() { 600 | if let colorFilter = _filters.inputFilters.prevFilter() { 601 | setColorFilter(colorFilter) 602 | } 603 | } 604 | 605 | // MARK: Delegate Members 606 | 607 | var videoFilter:String { 608 | get { 609 | return _currentVideoFilter.name 610 | } 611 | set { 612 | if let filter = _filters.videoFilters.getFilter(name: newValue) 613 | { 614 | setVideoFilter(filter) 615 | } 616 | } 617 | } 618 | 619 | var colorFilter:String { 620 | get { 621 | return _currentColorFilter.name 622 | } 623 | set { 624 | if let filter = _filters.inputFilters.getFilter(name: newValue) 625 | { 626 | setColorFilter(filter) 627 | } 628 | } 629 | } 630 | 631 | var lock:Bool = false { 632 | didSet { 633 | print("Setting lock: \(lock)") 634 | if _settingsDelegate == nil { 635 | if lock { 636 | hideUI() 637 | } else { 638 | showUI() 639 | } 640 | } 641 | showLockOverlay(lock) 642 | saveDefaults() 643 | } 644 | } 645 | 646 | var blur:Bool = false { 647 | didSet { 648 | print("Setting blur: \(blur)") 649 | _settingsDelegate?.setBlur(blur) 650 | renderer.applyBlur = blur 651 | saveDefaults() 652 | } 653 | } 654 | 655 | var invert:Bool = false { 656 | didSet { 657 | print("Setting invert: \(invert)") 658 | _settingsDelegate?.setInvert(invert) 659 | renderer.invertScreen = invert 660 | saveDefaults() 661 | } 662 | } 663 | 664 | var frontCamera:Bool = false { 665 | didSet { 666 | print("Setting front camera: \(frontCamera)") 667 | camera.useFrontCamera = frontCamera 668 | UIView.animate(withDuration: 0.5, animations: { 669 | () -> Void in 670 | if (self.frontCamera){ 671 | self._switchView.tintColor = self._buttonEnabledColor 672 | 673 | } else { 674 | self._switchView.tintColor = UIColor.white 675 | } 676 | }) 677 | 678 | saveDefaults() 679 | } 680 | } 681 | 682 | var autoHideUI:Bool = false { 683 | didSet { 684 | if !autoHideUI { 685 | _uiTimer?.invalidate() 686 | _uiTimer = nil 687 | } else { 688 | if _settingsDelegate == nil && enableUI { 689 | _uiTimer = Timer(timeInterval: TimeInterval(2.0), target: self, selector: #selector(MainViewController.hideUI), userInfo: nil, repeats: false) 690 | RunLoop.current.add(_uiTimer!, forMode: RunLoopMode.defaultRunLoopMode) 691 | } 692 | } 693 | _settingsDelegate?.setAutoHide(autoHideUI) 694 | saveDefaults() 695 | } 696 | } 697 | 698 | // MARK: Gesture Recognizers 699 | 700 | @IBAction func tapGestureRecognizer(_ sender: UITapGestureRecognizer) { 701 | if sender.state == .ended && !lock { 702 | if _settingsView.isHidden { 703 | showUI() 704 | } else { 705 | hideUI() 706 | } 707 | } 708 | } 709 | 710 | @IBAction func swipeGestureRecognizer(_ sender: UISwipeGestureRecognizer) { 711 | if sender.state == .ended && !lock { 712 | _swipeActions[sender.direction.rawValue]?() 713 | } 714 | } 715 | @IBAction func longPressRecognizer(_ sender: UILongPressGestureRecognizer) { 716 | if sender.state == UIGestureRecognizerState.began { 717 | lock = !lock 718 | } 719 | } 720 | 721 | @IBAction func switchButton(_ sender: AnyObject) { 722 | if camera.supportsFrontCamera { 723 | frontCamera = !frontCamera 724 | } 725 | } 726 | 727 | func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool { 728 | if (_isiPad) { 729 | _settingsDelegate = nil 730 | UIView.animate(withDuration: 0.5, animations: { 731 | () -> Void in 732 | self._settingsView.tintColor = UIColor.white 733 | }) 734 | saveDefaults() 735 | if lock { 736 | hideUI() 737 | } else { 738 | showUI() 739 | } 740 | } 741 | return true 742 | } 743 | 744 | func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { 745 | return UIModalPresentationStyle.overFullScreen 746 | } 747 | } 748 | 749 | -------------------------------------------------------------------------------- /AccessibleVideo/MetalView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalView.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 10/5/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Metal 12 | import QuartzCore 13 | 14 | protocol MetalViewDelegate { 15 | func render(_ view:MetalView) 16 | func resize(_ size:CGSize) 17 | } 18 | 19 | class MetalView:UIView { 20 | 21 | var delegate:MetalViewDelegate! = nil 22 | 23 | var device:MTLDevice! { 24 | return _device 25 | } 26 | 27 | var renderPassDescriptor:MTLRenderPassDescriptor? { 28 | if let drawable = self.currentDrawable { 29 | setupRenderPassDescriptorForTexture(drawable.texture) 30 | } else { 31 | _renderPassDescriptor = nil 32 | } 33 | return _renderPassDescriptor 34 | } 35 | 36 | var currentDrawable:CAMetalDrawable? { 37 | if _currentDrawable == nil { 38 | _currentDrawable = _metalLayer.nextDrawable() 39 | } 40 | return _currentDrawable 41 | } 42 | 43 | fileprivate var _layerSizeDidUpdate:Bool = false 44 | fileprivate weak var _metalLayer:CAMetalLayer! = nil 45 | fileprivate var _currentDrawable:CAMetalDrawable? = nil 46 | fileprivate var _renderPassDescriptor:MTLRenderPassDescriptor? = nil 47 | lazy fileprivate var _device:MTLDevice = MTLCreateSystemDefaultDevice()! 48 | 49 | 50 | override class var layerClass : AnyClass { 51 | return CAMetalLayer.self 52 | } 53 | 54 | func initCommon() { 55 | self.isOpaque = true 56 | self.backgroundColor = nil 57 | _metalLayer = self.layer as! CAMetalLayer 58 | _metalLayer.presentsWithTransaction = false 59 | _metalLayer.device = _device 60 | _metalLayer.pixelFormat = .bgra8Unorm 61 | _metalLayer.framebufferOnly = true 62 | } 63 | 64 | override func didMoveToWindow() { 65 | if let win = window { 66 | contentScaleFactor = win.screen.nativeScale 67 | } 68 | } 69 | 70 | override init(frame: CGRect) { 71 | super.init(frame: frame) 72 | initCommon() 73 | } 74 | 75 | required init?(coder: NSCoder) { 76 | super.init(coder: coder) 77 | initCommon() 78 | } 79 | 80 | func setupRenderPassDescriptorForTexture(_ texture:MTLTexture!) { 81 | if _renderPassDescriptor == nil { 82 | _renderPassDescriptor = MTLRenderPassDescriptor() 83 | _renderPassDescriptor!.colorAttachments[0].loadAction = .clear 84 | _renderPassDescriptor!.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0) 85 | _renderPassDescriptor!.colorAttachments[0].storeAction = .store 86 | } 87 | 88 | _renderPassDescriptor!.colorAttachments[0].texture = texture 89 | 90 | } 91 | 92 | func display() { 93 | autoreleasepool { 94 | if self._layerSizeDidUpdate { 95 | var drawableSize = self.bounds.size 96 | drawableSize.width *= self.contentScaleFactor 97 | drawableSize.height *= self.contentScaleFactor 98 | self._metalLayer.drawableSize = drawableSize 99 | self.delegate.resize(drawableSize) 100 | self._layerSizeDidUpdate = false 101 | } 102 | self.delegate.render(self) 103 | self._currentDrawable = nil 104 | } 105 | } 106 | 107 | override func layoutSubviews() { 108 | super.layoutSubviews() 109 | _layerSizeDidUpdate = true 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /AccessibleVideo/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.swift 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 11/9/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol SettingsViewDelegate { 12 | func setBlur(_ on:Bool) 13 | func setAutoHide(_ on:Bool) 14 | func setInvert(_ on:Bool) 15 | } 16 | 17 | class SettingsViewController: UIViewController, SettingsViewDelegate { 18 | 19 | var delegate:ControlsDelegate! = nil 20 | 21 | @IBOutlet weak var blurSwitch: UISwitch! 22 | 23 | @IBOutlet weak var autoHideSwitch: UISwitch! 24 | 25 | @IBOutlet weak var invertSwitch: UISwitch! 26 | 27 | lazy fileprivate var _isiPad:Bool = (UIDevice.current.userInterfaceIdiom == .pad) 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | if !_isiPad { 32 | let barButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(SettingsViewController.performUnwind)) 33 | self.navigationItem.rightBarButtonItem = barButton 34 | } 35 | // Do any additional setup after loading the view. 36 | } 37 | 38 | override func didReceiveMemoryWarning() { 39 | super.didReceiveMemoryWarning() 40 | // Dispose of any resources that can be recreated. 41 | } 42 | 43 | override func viewWillAppear(_ animated: Bool) { 44 | blurSwitch.setOn(delegate.blur, animated: false) 45 | autoHideSwitch.setOn(delegate.autoHideUI, animated: false) 46 | invertSwitch.setOn(delegate.invert, animated: false) 47 | } 48 | 49 | func setBlur(_ on: Bool) { 50 | if blurSwitch.isOn != on { 51 | blurSwitch.setOn(on, animated: true) 52 | } 53 | } 54 | 55 | @IBAction func handleBlur (_ sender: UISwitch!) { 56 | if delegate.blur != blurSwitch.isOn { 57 | delegate.blur = blurSwitch.isOn 58 | } 59 | } 60 | 61 | func setAutoHide(_ on: Bool) { 62 | if autoHideSwitch.isOn != on { 63 | autoHideSwitch.setOn(on, animated: true) 64 | } 65 | } 66 | 67 | @IBAction func handleAutoHideUI(_ sender: UISwitch) { 68 | if delegate.autoHideUI != autoHideSwitch.isOn { 69 | delegate.autoHideUI = autoHideSwitch.isOn 70 | } 71 | } 72 | 73 | func setInvert(_ on: Bool) { 74 | if invertSwitch.isOn != on { 75 | invertSwitch.setOn(on, animated: true) 76 | } 77 | } 78 | 79 | @IBAction func handleInvertUI(_ sender: AnyObject) { 80 | if delegate.invert != invertSwitch.isOn { 81 | delegate.invert = invertSwitch.isOn 82 | } 83 | } 84 | 85 | func performUnwind() { 86 | self.performSegue(withIdentifier: "unwind", sender: self) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /AccessibleVideo/Shaders/Base.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Shaders.metal 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 9/15/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | 10 | // Base shaders 11 | // 12 | // These are required for program to run 13 | // 14 | 15 | #include "Common.metal" 16 | 17 | vertex VertexOut defaultVertex( VertexIn vert [[ stage_in ]], unsigned int vid [[ vertex_id ]]) 18 | { 19 | VertexOut outVertices; 20 | outVertices.m_Position = float4(vert.m_Position,0.0,1.0); 21 | outVertices.m_TexCoord = vert.m_TexCoord; 22 | return outVertices; 23 | } 24 | 25 | fragment half4 yuv_rgb(YUV_SHADER_ARGS) 26 | { 27 | float3 yuv; 28 | yuv.x = lumaTex.sample(bilinear, inFrag.m_TexCoord).r; 29 | yuv.yz = chromaTex.sample(bilinear,inFrag.m_TexCoord).rg - float2(0.5); 30 | return half4(half3(colorParameters->yuvToRGB * yuv),yuv.x); 31 | } 32 | 33 | fragment half4 yuv_grayscale(YUV_SHADER_ARGS) 34 | { 35 | return half4(lumaTex.sample(bilinear, inFrag.m_TexCoord).r); 36 | } 37 | 38 | fragment half4 blit(FILTER_SHADER_ARGS_LAST_ONLY) 39 | { 40 | half4 color = half4(lastStage.sample(bilinear, inFrag.m_TexCoord).rgb,1.0); 41 | return color; 42 | } 43 | 44 | 45 | fragment half4 invert(FILTER_SHADER_ARGS_LAST_ONLY) 46 | { 47 | half3 inverse = half3(1.0) - lastStage.sample(bilinear, inFrag.m_TexCoord).rgb; 48 | return half4(inverse,1.0); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /AccessibleVideo/Shaders/Blur.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Blur.metal 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 11/9/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | 10 | // Blur shaders 11 | // 12 | // These are required for program to run, and apply a seperable gaussian blur to the image 13 | // 14 | 15 | 16 | #include "Common.metal" 17 | 18 | 19 | 20 | fragment half4 BlurX(VertexOut inFrag [[ stage_in ]], 21 | texture2d sourceTexture [[ texture(0) ]], 22 | sampler nearest [[ sampler(0) ]]) 23 | { 24 | half4 m21 = sourceTexture.sample(nearest, inFrag.m_TexCoord,int2(-1,0)); 25 | half4 m22 = sourceTexture.sample(nearest, inFrag.m_TexCoord,int2(0,0)); 26 | half4 m23 = sourceTexture.sample(nearest, inFrag.m_TexCoord,int2(+1,0)); 27 | 28 | half4 color = 0.25 * (m21 + m23) + 0.5 * m22; 29 | return color; 30 | } 31 | 32 | fragment half4 BlurY(VertexOut inFrag [[ stage_in ]], 33 | texture2d sourceTexture [[ texture(0) ]], 34 | sampler nearest [[ sampler(0) ]]) 35 | { 36 | half4 m12 = sourceTexture.sample(nearest, inFrag.m_TexCoord,int2(0,+1)); 37 | half4 m22 = sourceTexture.sample(nearest, inFrag.m_TexCoord,int2(0,0)); 38 | half4 m32 = sourceTexture.sample(nearest, inFrag.m_TexCoord,int2(0,-1)); 39 | 40 | half4 color = 0.25 * (m12 + m32) + 0.5 * m22; 41 | return color; 42 | } 43 | 44 | // 45 | // High quality linear sampled gaussian blur 46 | // Source: http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ 47 | // 48 | 49 | constant half weights[] = { 0.2270270270, 0.3162162162, 0.0702702703 }; 50 | struct BlurParameters { 51 | float2 xOffsets[3]; 52 | float2 yOffsets[3]; 53 | }; 54 | 55 | fragment half4 BlurX_HQ(VertexOut inFrag [[ stage_in ]], 56 | texture2d sourceTexture [[ texture(0) ]], 57 | sampler bilinear [[ sampler(1) ]], 58 | constant BlurParameters *blurParameters [[ buffer(0) ]]) 59 | { 60 | half4 color = half4(0.0); 61 | 62 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord - blurParameters->xOffsets[2]) * weights[2]; 63 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord - blurParameters->xOffsets[1]) * weights[1]; 64 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord) * weights[0]; 65 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord + blurParameters->xOffsets[1]) * weights[1]; 66 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord + blurParameters->xOffsets[2]) * weights[2]; 67 | return color; 68 | } 69 | 70 | fragment half4 BlurY_HQ(VertexOut inFrag [[ stage_in ]], 71 | texture2d sourceTexture [[ texture(0) ]], 72 | sampler bilinear [[ sampler(1) ]], 73 | constant BlurParameters *blurParameters [[ buffer(0) ]]) 74 | { 75 | half4 color = half4(0.0); 76 | 77 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord - blurParameters->yOffsets[2]) * weights[2]; 78 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord - blurParameters->yOffsets[1]) * weights[1]; 79 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord) * weights[0]; 80 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord + blurParameters->yOffsets[1]) * weights[1]; 81 | color += sourceTexture.sample(bilinear, inFrag.m_TexCoord + blurParameters->yOffsets[2]) * weights[2]; 82 | return color; 83 | } -------------------------------------------------------------------------------- /AccessibleVideo/Shaders/Canny.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Canny.metal 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 11/11/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | #include "Common.metal" 10 | 11 | constant half invPi = 1.0/3.1415926535; 12 | 13 | 14 | half unpack(half angle); 15 | 16 | // sample the angle 17 | half unpack(half angle) 18 | { 19 | // convert it from 0.0 - 1.0 to -pi/2 to pi/2 20 | half theta = (angle - 0.5) * 180.0; 21 | if (theta < 0.0) 22 | theta += 180.0; 23 | return theta; 24 | } 25 | 26 | 27 | fragment half4 CannySobelPass(FILTER_SHADER_ARGS_FRAME_ONLY) 28 | { 29 | 30 | half m11 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1,+1)).a; 31 | half m12 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2( 0,+1)).a; 32 | half m13 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1,+1)).a; 33 | half m21 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1, 0)).a; 34 | half m22 = currentFrame.sample(bilinear, inFrag.m_TexCoord).a; 35 | half m23 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1, 0)).a; 36 | half m31 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1,-1)).a; 37 | half m32 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2( 0,-1)).a; 38 | half m33 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1,-1)).a; 39 | 40 | half2 hv; 41 | half m31m13 = m31 - m13; 42 | half m11m33 = m33 - m11; 43 | half m32m12 = m32 - m12; 44 | half m21m23 = m21 - m23; 45 | hv.x = m32m12 + m32m12 + m11m33 + m31m13; 46 | hv.y = m21m23 + m21m23 - m11m33 + m31m13; 47 | 48 | half sobel = length(hv); 49 | 50 | half theta = atan2(hv.x,hv.y) * invPi + 0.5; 51 | 52 | half4 color = half4(m22,sobel,theta,1.0); 53 | return color; 54 | } 55 | 56 | 57 | 58 | 59 | fragment half4 CannyMagnitude(FILTER_SHADER_ARGS_LAST_ONLY) 60 | { 61 | half2 temp = lastStage.sample(bilinear, inFrag.m_TexCoord).gb; 62 | half angle = unpack(temp.g); 63 | half m11 = lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1,+1)).g; 64 | half m12 = lastStage.sample(bilinear, inFrag.m_TexCoord,int2( 0,+1)).g; 65 | half m13 = lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1,+1)).g; 66 | half m21 = lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1, 0)).g; 67 | half m22 = temp.r; 68 | half m23 = lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1, 0)).g; 69 | half m31 = lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1,-1)).g; 70 | half m32 = lastStage.sample(bilinear, inFrag.m_TexCoord,int2( 0,-1)).g; 71 | half m33 = lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1,-1)).g; 72 | 73 | bool test = ((angle <= 22.5 || angle >= 157.5)&&(m22 > m21 && m22 > m23)) 74 | || ((angle <= 112.5 && angle >= 77.5)&&(m22 > m12 && m22 > m32)) 75 | || ((angle <= 77.5 && angle >= 22.5)&&(m22 > m11 && m22 > m33)) 76 | || ((angle >= 112.5 && angle <= 157.5)&&(m22 > m13 && m22 > m31)); 77 | 78 | half4 color; 79 | 80 | if (test) 81 | color = half4(m22,m22,m22,1.0); 82 | else 83 | color = half4(half3(0.0),1.0); 84 | 85 | return color; 86 | } 87 | 88 | fragment half4 CannyThreshold(FILTER_SHADER_ARGS_LAST_ONLY) 89 | { 90 | half m22 = lastStage.sample(bilinear, inFrag.m_TexCoord).r; 91 | half4 color = half4(half3(0.0),1.0); 92 | 93 | half highThreshold = HIGH_THRESHOLD; 94 | 95 | if (m22 >= highThreshold ) 96 | { 97 | color.rgb = PRIMARY_COLOR.rgb; 98 | } 99 | else if (m22 >= LOW_THRESHOLD){ 100 | #define m11 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1,+1)).r 101 | #define m12 lastStage.sample(bilinear, inFrag.m_TexCoord,int2( 0,+1)).r 102 | #define m13 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1,+1)).r 103 | #define m21 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1, 0)).r 104 | #define m23 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1, 0)).r 105 | #define m31 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1,-1)).r 106 | #define m32 lastStage.sample(bilinear, inFrag.m_TexCoord,int2( 0,-1)).r 107 | #define m33 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1,-1)).r 108 | if ((m11 >= highThreshold) || (m12 >= highThreshold) || (m13 >= highThreshold ) || 109 | (m21 >= highThreshold) || (m23 >= highThreshold) || 110 | (m31 >= highThreshold) || (m32 >= highThreshold) || (m33 >= highThreshold)) 111 | { 112 | color.rgb = PRIMARY_COLOR.rgb; 113 | } else { 114 | color.rgb = SECONDARY_COLOR.rgb; 115 | } 116 | } 117 | return color; 118 | } 119 | 120 | fragment half4 CannyThresholdComposite(FILTER_SHADER_ARGS) 121 | { 122 | half highThreshold = HIGH_THRESHOLD; 123 | 124 | half m22 = lastStage.sample(bilinear, inFrag.m_TexCoord).r; 125 | 126 | half3 color = originalFrame.sample(bilinear, inFrag.m_TexCoord).rgb; 127 | 128 | if (m22 >= highThreshold ) 129 | { 130 | half4 blendColor = PRIMARY_COLOR; 131 | color *= (1.0 - blendColor.a); 132 | color += (blendColor.rgb * blendColor.a); 133 | } else if (m22 >= LOW_THRESHOLD){ 134 | #define m11 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1,+1)).r 135 | #define m12 lastStage.sample(bilinear, inFrag.m_TexCoord,int2( 0,+1)).r 136 | #define m13 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1,+1)).r 137 | #define m21 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1, 0)).r 138 | #define m23 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1, 0)).r 139 | #define m31 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1,-1)).r 140 | #define m32 lastStage.sample(bilinear, inFrag.m_TexCoord,int2( 0,-1)).r 141 | #define m33 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1,-1)).r 142 | 143 | if ((m11 >= highThreshold) || (m12 >= highThreshold) || (m13 >= highThreshold ) || 144 | (m21 >= highThreshold) || (m23 >= highThreshold) || 145 | (m31 >= highThreshold) || (m32 >= highThreshold) || (m33 >= highThreshold)) 146 | { 147 | half4 blendColor = PRIMARY_COLOR; 148 | color *= (1.0 - blendColor.a); 149 | color += (blendColor.rgb * blendColor.a); 150 | } else { 151 | half4 blendColor = SECONDARY_COLOR; 152 | color *= (1.0 - blendColor.a); 153 | color += (blendColor.rgb * blendColor.a); 154 | } 155 | } 156 | 157 | return half4(color,1.0); 158 | } 159 | 160 | fragment half4 CannyComic(FILTER_SHADER_ARGS) 161 | { 162 | // parameters that define the comic effect 163 | #define LINE_SLOPE -0.9h 164 | #define LINE_INTERVAL 10.0h 165 | #define LINE_STRENGTH 1.0h 166 | #define BLACK_THRESHOLD 0.2h 167 | #define WHITE_THRESHOLD 0.45h 168 | half highThreshold = HIGH_THRESHOLD; 169 | 170 | bool result = false; 171 | half m22 = lastStage.sample(bilinear, inFrag.m_TexCoord).r; 172 | half3 color; 173 | 174 | if (m22 >= highThreshold ) 175 | { 176 | result = true; 177 | } else if (m22 >= LOW_THRESHOLD){ 178 | #define m11 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1,+1)).r 179 | #define m12 lastStage.sample(bilinear, inFrag.m_TexCoord,int2( 0,+1)).r 180 | #define m13 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1,+1)).r 181 | #define m21 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1, 0)).r 182 | #define m23 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1, 0)).r 183 | #define m31 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(-1,-1)).r 184 | #define m32 lastStage.sample(bilinear, inFrag.m_TexCoord,int2( 0,-1)).r 185 | #define m33 lastStage.sample(bilinear, inFrag.m_TexCoord,int2(+1,-1)).r 186 | 187 | if ((m11 >= highThreshold) || (m12 >= highThreshold) || (m13 >= highThreshold ) || 188 | (m21 >= highThreshold) || (m23 >= highThreshold) || 189 | (m31 >= highThreshold) || (m32 >= highThreshold) || (m33 >= highThreshold)) 190 | { 191 | result = true; 192 | } 193 | } 194 | 195 | // set pixel to white if it passed, or black otherwise 196 | 197 | if (result) { 198 | color = SECONDARY_COLOR.rgb; 199 | } else { 200 | half gray = originalFrame.sample(bilinear, inFrag.m_TexCoord).a; 201 | if (gray > WHITE_THRESHOLD) 202 | { 203 | color = PRIMARY_COLOR.rgb; 204 | } else if (gray < BLACK_THRESHOLD) 205 | { 206 | color = SECONDARY_COLOR.rgb; 207 | } else 208 | { 209 | half2 pixel = half2(inFrag.m_TexCoord) * half2(lastStage.get_width(),lastStage.get_height()); 210 | half b = LINE_SLOPE * pixel.x - pixel.y; 211 | color = (floor(fmod(b,LINE_INTERVAL)) - LINE_STRENGTH > 0.0) ? PRIMARY_COLOR.rgb : SECONDARY_COLOR.rgb; 212 | } 213 | } 214 | return half4(color,1.0); 215 | } 216 | 217 | -------------------------------------------------------------------------------- /AccessibleVideo/Shaders/Colorblind.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Colorblind.metal 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 12/11/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | #include "Common.metal" 10 | 11 | fragment half4 tritanope(FILTER_SHADER_ARGS_FRAME_ONLY) 12 | { 13 | half3x3 errorshift = {{0.0, 0.7, 0.7}, 14 | {0.0, 1.0, 0.0}, 15 | {0.0, 0.0, 1.0}}; 16 | 17 | half3x3 tritanope = {{ 0.49325795, 0.49325586, -3.01086514}, 18 | { 0.50674879, 0.50673758, 3.01090517}, 19 | { 0.00000075, -0.00000073, 1.00000447}}; 20 | 21 | half3 rgb = originalFrame.sample(bilinear, inFrag.m_TexCoord).rgb; 22 | half3 shift = errorshift * (rgb - (tritanope * rgb)); 23 | return half4(shift + rgb,1.0); 24 | } 25 | 26 | fragment half4 deuteranope(FILTER_SHADER_ARGS_FRAME_ONLY) 27 | { 28 | half3x3 errorshift = {{0.0, 0.7, 0.7}, 29 | {0.0, 1.0, 0.0}, 30 | {0.0, 0.0, 1.0}}; 31 | 32 | half3x3 deuteranope = {{ 0.29275078, 0.2927497 , -0.02233648}, 33 | { 0.70725186, 0.70724921, 0.02233656}, 34 | { 0.00000052, -0.00000021, 1.00000002}}; 35 | 36 | half3 rgb = originalFrame.sample(bilinear, inFrag.m_TexCoord).rgb; 37 | half3 shift = errorshift * (rgb - (deuteranope * rgb)); 38 | return half4(shift + rgb,1.0); 39 | } 40 | 41 | fragment half4 protanope(FILTER_SHADER_ARGS_FRAME_ONLY) 42 | { 43 | half3x3 errorshift = {{0.0, 0.7, 0.7}, 44 | {0.0, 1.0, 0.0}, 45 | {0.0, 0.0, 1.0}}; 46 | 47 | half3x3 protanope = {{ 0.11238227, 0.112383 , 0.00400576}, 48 | { 0.88761197, 0.88761773, -0.00400573}, 49 | {-0.0000012 , 0.00000015, 1.00000001}}; 50 | 51 | half3 rgb = originalFrame.sample(bilinear, inFrag.m_TexCoord).rgb; 52 | half3 shift = errorshift * (rgb - (protanope * rgb)); 53 | return half4(shift + rgb,1.0); 54 | } 55 | -------------------------------------------------------------------------------- /AccessibleVideo/Shaders/Common.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Common.metal 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 12/11/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | 10 | 11 | // Common shader definitions 12 | // 13 | // These are required for program to run, and ensure consistent behavior across shaders 14 | // 15 | 16 | 17 | #include 18 | #include 19 | 20 | using namespace metal; 21 | 22 | struct VertexIn 23 | { 24 | float2 m_Position [[ attribute(0) ]]; 25 | float2 m_TexCoord [[ attribute(1) ]]; 26 | }; 27 | 28 | struct VertexOut 29 | { 30 | float4 m_Position [[ position ]]; 31 | float2 m_TexCoord [[ user(texturecoord) ]]; 32 | }; 33 | 34 | struct ColorParameters 35 | { 36 | float3x3 yuvToRGB; 37 | }; 38 | 39 | struct FilterParameters 40 | { 41 | float lowThreshold; 42 | float highThreshold; 43 | uint primaryColor; 44 | uint secondaryColor; 45 | }; 46 | 47 | #define PRIMARY_COLOR unpack_unorm4x8_to_half(filterParameters->primaryColor) 48 | #define SECONDARY_COLOR unpack_unorm4x8_to_half(filterParameters->secondaryColor) 49 | #define HIGH_THRESHOLD half(filterParameters->highThreshold) 50 | #define LOW_THRESHOLD half(filterParameters->lowThreshold) 51 | 52 | #define YUV_SHADER_ARGS VertexOut inFrag [[ stage_in ]],\ 53 | texture2d lumaTex [[ texture(0) ]],\ 54 | texture2d chromaTex [[ texture(1) ]],\ 55 | sampler bilinear [[ sampler(1) ]], \ 56 | constant ColorParameters *colorParameters [[ buffer(0) ]] 57 | 58 | #define FILTER_SHADER_ARGS VertexOut inFrag [[ stage_in ]],\ 59 | texture2d lastStage [[ texture(0) ]],\ 60 | texture2d currentFrame [[ texture(1) ]],\ 61 | texture2d originalFrame [[ texture(2) ]],\ 62 | sampler bilinear [[ sampler(1) ]], \ 63 | constant FilterParameters *filterParameters [[ buffer(0) ]] 64 | 65 | #define FILTER_SHADER_ARGS_LAST_ONLY VertexOut inFrag [[ stage_in ]],\ 66 | texture2d lastStage [[ texture(0) ]],\ 67 | sampler bilinear [[ sampler(1) ]], \ 68 | constant FilterParameters *filterParameters [[ buffer(0) ]] 69 | 70 | 71 | #define FILTER_SHADER_ARGS_FRAME_ONLY VertexOut inFrag [[ stage_in ]],\ 72 | texture2d currentFrame [[ texture(1) ]],\ 73 | texture2d originalFrame [[ texture(2) ]],\ 74 | sampler bilinear [[ sampler(1) ]], \ 75 | constant FilterParameters *filterParameters [[ buffer(0) ]] -------------------------------------------------------------------------------- /AccessibleVideo/Shaders/Shaders.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sobel 6 | 7 | fragment 8 | sobel_color 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /AccessibleVideo/Shaders/Sobel.metal: -------------------------------------------------------------------------------- 1 | // 2 | // Canny.metal 3 | // AccessibleVideo 4 | // 5 | // Created by Luke Groeninger on 10/7/14. 6 | // Copyright (c) 2014 Luke Groeninger. All rights reserved. 7 | // 8 | 9 | #include "Common.metal" 10 | 11 | fragment half4 sobel_color(FILTER_SHADER_ARGS_FRAME_ONLY) 12 | { 13 | half3 m11 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1,+1)).rgb; 14 | half3 m12 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(0,+1)).rgb; 15 | half3 m13 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1,+1)).rgb; 16 | half3 m21 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1,0)).rgb; 17 | // half3 m22 = currentFrame.sample(bilinear, inFrag.m_TexCoord).rgb; 18 | half3 m23 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1,0)).rgb; 19 | half3 m31 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1,-1)).rgb; 20 | half3 m32 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(0,-1)).rgb; 21 | half3 m33 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1,-1)).rgb; 22 | 23 | half3 m31m13 = m31 - m13; 24 | half3 m11m33 = m33 - m11; 25 | half3 m32m12 = m32 - m12; 26 | half3 m21m23 = m21 - m23; 27 | half3 H = m32m12 + m32m12 + m11m33 + m31m13; 28 | half3 V = m21m23 + m21m23 - m11m33 + m31m13; 29 | 30 | half3 sobel = sqrt(H*H+V*V); 31 | 32 | half4 color = half4(sobel,1.0); 33 | return color; 34 | } 35 | 36 | 37 | fragment half4 sobel_composite(FILTER_SHADER_ARGS_FRAME_ONLY) 38 | { 39 | half4 blendColor = PRIMARY_COLOR; 40 | half m11 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1,+1)).a; 41 | half m12 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(0,+1)).a; 42 | half m13 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1,+1)).a; 43 | half m21 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1,0)).a; 44 | half m23 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1,0)).a; 45 | half m31 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(-1,-1)).a; 46 | half m32 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(0,-1)).a; 47 | half m33 = currentFrame.sample(bilinear, inFrag.m_TexCoord,int2(+1,-1)).a; 48 | 49 | half2 hv; 50 | half m31m13 = m31 - m13; 51 | half m11m33 = m33 - m11; 52 | half m32m12 = m32 - m12; 53 | half m21m23 = m21 - m23; 54 | hv.x = m32m12 + m32m12 + m11m33 + m31m13; 55 | hv.y = m21m23 + m21m23 - m11m33 + m31m13; 56 | 57 | blendColor.a *= length(hv); 58 | 59 | half3 color = originalFrame.sample(bilinear, inFrag.m_TexCoord).rgb * (1.0 - blendColor.a); 60 | color += blendColor.rgb * blendColor.a; 61 | return half4(color,1.0); 62 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## AccessibleVideo 2 | 3 | This project is an iOS app intended to help people with visual deficiencies be able to see more of the world around them. To achieve this it uses the GPU to perform image processing of real-time video from the camera in an attempt to make key details more visible. 4 | 5 | It is a ground-up rewrite of the [GLVideoFilter](https://github.com/dghost/GLVideoFilter) project using Swift and Metal, and eventually will meet or exceed it in terms of functionality. 6 | 7 | #### Features 8 | 9 | * Flexible processing pipeline written using Swift and Metal 10 | * Can apply separate color and image filters 11 | * Can apply the following color filters: 12 | * Full color 13 | * Grayscale 14 | * Protonopia simulation 15 | * Deuteranopia simulation 16 | * Tritanopia simulation 17 | * Can apply the following image processing filters: 18 | * No filter 19 | * Raw Sobel filter 20 | * Composite Sobel filter (overlays on video) 21 | * Raw Canny filter 22 | * Composite Canny filter (overlays on video) 23 | * Comic filter (experimental, cartoon shading) 24 | * Protanopia correction (Daltonization) 25 | * Deuteranopia correction (Daltonization) 26 | * Tritanopia correction (Daltonization) 27 | * Can additionally invert the resulting video 28 | * Uses either a separable gaussian blur or a high-quality [linear sampled gaussian blur](http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/) for noise reduction. 29 | * Easy to use gesture UI 30 | * iCloud support for sychronizing settings across devices 31 | * Front and rear camera support on certain devices 32 | 33 | #### Requirements 34 | 35 | * A7 or A8 iOS device (iPhone 5s+, iPad Air+, iPad Mini 2+) 36 | * iOS 8.0 or higher 37 | * Cameras require 60fps support 38 | * Xcode 6.1 or higher to build 39 | 40 | This was tested primarily on an iPhone 6, so milage may vary on other devices. 41 | 42 | 43 | #### Interface 44 | 45 | Currently, the interface is rudimentary but functional. From the main view it supports the following gestures: 46 | 47 | * Tap to show/dismiss primary UI 48 | * Long press to lock/unlock UI 49 | * Swipe left/right to cycle through gestures 50 | 51 | Additionally, there is an overlay UI that allows you to switch between front/back cameras on supported devices and go to a settings menu. 52 | 53 | #### Algorithms Used 54 | 55 | 56 | * [Canny edge detectors](http://en.wikipedia.org/wiki/Canny_edge_detector) as the primary edge detector and as a first pass for the Sobel operator 57 | * [Sobel operator](http://en.wikipedia.org/wiki/Sobel_operator) for an advanced edge detector 58 | * [Daltonization](http://www.daltonize.org/search/label/Daltonize) for colorblindness simulation and correction im/Gaussian_blur) as a pre-pass to reduce noise in the images 59 | 60 | 61 | #### Dependencies and Acknowledgements 62 | 63 | A huge thanks go out to: 64 | 65 | * [MBProgressHUD](https://github.com/jdg/MBProgressHUD) for making the translucent HUD easy. 66 | * [Icons8](http://icons8.com) for releasing a whole bunch of icons under [CC BY-ND 3.0 license](https://creativecommons.org/licenses/by-nd/3.0/) --------------------------------------------------------------------------------