├── .github └── FUNDING.yml ├── README.md ├── VNImageScanner.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── varunnaharia.xcuserdatad │ └── xcschemes │ ├── VNImageScanner.xcscheme │ └── xcschememanagement.plist ├── VNImageScanner ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── capture_button.imageset │ │ ├── Contents.json │ │ └── capture_button@2x.png │ └── focusIndicator.imageset │ │ ├── Contents.json │ │ ├── focusIndicator.png │ │ └── focusIndicator@2x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── VNCameraScanner │ ├── VNCameraScanner.swift │ └── VNCameraScanner.xib ├── VNImageScanner.xcdatamodeld │ ├── .xccurrentversion │ └── VNImageScanner.xcdatamodel │ │ └── contents └── ViewController.swift ├── VNImageScannerTests ├── Info.plist └── VNImageScannerTests.swift └── VNImageScannerUITests ├── Info.plist └── VNImageScannerUITests.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: varun-naharia 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/github/issues/varun-naharia/VNImageScanner) 2 | ![](https://img.shields.io/github/forks/varun-naharia/VNImageScanner) 3 | ![](https://img.shields.io/github/stars/varun-naharia/VNImageScanner) 4 | ![GitHub All Releases](https://img.shields.io/github/downloads/varun-naharia/VNImageScanner/total) 5 | 6 | ![Screenshot](https://raw.githubusercontent.com/mmackh/IPDFCameraViewController/master/mockup.png) 7 | 8 | # VNImageScanner 9 | 10 | Welcome to the spiritual successor of [IPDFCameraViewController](https://github.com/mmackh/IPDFCameraViewController) and [MAImagePickerController](https://github.com/mmackh/MAImagePickerController-of-InstaPDF), that tries to unite a usable & simple camera component class into a single UIView. Initially written as an essential component of InstaPDF 4.0 for [instapdf.com](https://instapdf.com), it seemed too useful to keep closed source. Plus we're celebrating our 100,000 document upload 🎉🎉🎉 11 | 12 | Leave all the hard work dealing with AVFoundation, border detection and OpenGL up to VNImageScanner. It includes: 13 | 14 | - Live border detection & perspective correction 15 | - Flash / Torch 16 | - Image filters 17 | - Simple API 18 | 19 | **WARNING: MINIMUM iOS VERSION REQUIREMENT: 8.0** 20 | 21 | Take a look at the sample project to find out how to use it. 22 | 23 | 24 | ## Installation 25 | 26 | ### Manual 27 | 28 | To manually install the framework, drag and drop the `VNCameraScanner/VNCameraScanner.swift` files into your project. 29 | 30 | 31 | ## Author 32 | Varun Naharia | Stackoverflow: [varun-naharia](http://stackoverflow.com/users/3851580/varun-naharia) | Web: [technaharia.in](http://technaharia.in) 33 | 34 | ## Todo's 35 | 36 | - Include more filters 37 | - Smoother animation between border detection frames 38 | - Improve confidence 39 | 40 | Improvements are needed so pull requests are welcome. 41 | -------------------------------------------------------------------------------- /VNImageScanner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AB6229B31E9CF37A00CB9510 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6229B21E9CF37A00CB9510 /* AppDelegate.swift */; }; 11 | AB6229B51E9CF37A00CB9510 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6229B41E9CF37A00CB9510 /* ViewController.swift */; }; 12 | AB6229BB1E9CF37A00CB9510 /* VNImageScanner.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AB6229B91E9CF37A00CB9510 /* VNImageScanner.xcdatamodeld */; }; 13 | AB6229BD1E9CF37A00CB9510 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AB6229BC1E9CF37A00CB9510 /* Assets.xcassets */; }; 14 | AB6229C01E9CF37A00CB9510 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AB6229BE1E9CF37A00CB9510 /* LaunchScreen.storyboard */; }; 15 | AB6229CB1E9CF37B00CB9510 /* VNImageScannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6229CA1E9CF37B00CB9510 /* VNImageScannerTests.swift */; }; 16 | AB6229D61E9CF37B00CB9510 /* VNImageScannerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6229D51E9CF37B00CB9510 /* VNImageScannerUITests.swift */; }; 17 | AB6229E51E9CFD9000CB9510 /* VNCameraScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB6229E41E9CFD9000CB9510 /* VNCameraScanner.swift */; }; 18 | AB84312A1E9E40EF002D0CA1 /* VNCameraScanner.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB8431291E9E40EF002D0CA1 /* VNCameraScanner.xib */; }; 19 | AB84312D1E9E4C02002D0CA1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AB84312B1E9E4C02002D0CA1 /* Main.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | AB6229C71E9CF37B00CB9510 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = AB6229A71E9CF37A00CB9510 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = AB6229AE1E9CF37A00CB9510; 28 | remoteInfo = VNImageScanner; 29 | }; 30 | AB6229D21E9CF37B00CB9510 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = AB6229A71E9CF37A00CB9510 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = AB6229AE1E9CF37A00CB9510; 35 | remoteInfo = VNImageScanner; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | AB6229AF1E9CF37A00CB9510 /* VNImageScanner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VNImageScanner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | AB6229B21E9CF37A00CB9510 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 42 | AB6229B41E9CF37A00CB9510 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 43 | AB6229BA1E9CF37A00CB9510 /* VNImageScanner.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = VNImageScanner.xcdatamodel; sourceTree = ""; }; 44 | AB6229BC1E9CF37A00CB9510 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 45 | AB6229BF1E9CF37A00CB9510 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 46 | AB6229C11E9CF37A00CB9510 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | AB6229C61E9CF37B00CB9510 /* VNImageScannerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VNImageScannerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | AB6229CA1E9CF37B00CB9510 /* VNImageScannerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VNImageScannerTests.swift; sourceTree = ""; }; 49 | AB6229CC1E9CF37B00CB9510 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | AB6229D11E9CF37B00CB9510 /* VNImageScannerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = VNImageScannerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | AB6229D51E9CF37B00CB9510 /* VNImageScannerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VNImageScannerUITests.swift; sourceTree = ""; }; 52 | AB6229D71E9CF37B00CB9510 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | AB6229E41E9CFD9000CB9510 /* VNCameraScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VNCameraScanner.swift; sourceTree = ""; }; 54 | AB8431291E9E40EF002D0CA1 /* VNCameraScanner.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = VNCameraScanner.xib; sourceTree = ""; }; 55 | AB84312C1E9E4C02002D0CA1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | AB6229AC1E9CF37A00CB9510 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | AB6229C31E9CF37B00CB9510 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | AB6229CE1E9CF37B00CB9510 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | AB6229A61E9CF37A00CB9510 = { 84 | isa = PBXGroup; 85 | children = ( 86 | AB6229B11E9CF37A00CB9510 /* VNImageScanner */, 87 | AB6229C91E9CF37B00CB9510 /* VNImageScannerTests */, 88 | AB6229D41E9CF37B00CB9510 /* VNImageScannerUITests */, 89 | AB6229B01E9CF37A00CB9510 /* Products */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | AB6229B01E9CF37A00CB9510 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | AB6229AF1E9CF37A00CB9510 /* VNImageScanner.app */, 97 | AB6229C61E9CF37B00CB9510 /* VNImageScannerTests.xctest */, 98 | AB6229D11E9CF37B00CB9510 /* VNImageScannerUITests.xctest */, 99 | ); 100 | name = Products; 101 | sourceTree = ""; 102 | }; 103 | AB6229B11E9CF37A00CB9510 /* VNImageScanner */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | AB6229E31E9CFD5B00CB9510 /* VNCameraScanner */, 107 | AB6229B21E9CF37A00CB9510 /* AppDelegate.swift */, 108 | AB6229B41E9CF37A00CB9510 /* ViewController.swift */, 109 | AB6229BC1E9CF37A00CB9510 /* Assets.xcassets */, 110 | AB84312B1E9E4C02002D0CA1 /* Main.storyboard */, 111 | AB6229BE1E9CF37A00CB9510 /* LaunchScreen.storyboard */, 112 | AB6229C11E9CF37A00CB9510 /* Info.plist */, 113 | AB6229B91E9CF37A00CB9510 /* VNImageScanner.xcdatamodeld */, 114 | ); 115 | path = VNImageScanner; 116 | sourceTree = ""; 117 | }; 118 | AB6229C91E9CF37B00CB9510 /* VNImageScannerTests */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | AB6229CA1E9CF37B00CB9510 /* VNImageScannerTests.swift */, 122 | AB6229CC1E9CF37B00CB9510 /* Info.plist */, 123 | ); 124 | path = VNImageScannerTests; 125 | sourceTree = ""; 126 | }; 127 | AB6229D41E9CF37B00CB9510 /* VNImageScannerUITests */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | AB6229D51E9CF37B00CB9510 /* VNImageScannerUITests.swift */, 131 | AB6229D71E9CF37B00CB9510 /* Info.plist */, 132 | ); 133 | path = VNImageScannerUITests; 134 | sourceTree = ""; 135 | }; 136 | AB6229E31E9CFD5B00CB9510 /* VNCameraScanner */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | AB6229E41E9CFD9000CB9510 /* VNCameraScanner.swift */, 140 | AB8431291E9E40EF002D0CA1 /* VNCameraScanner.xib */, 141 | ); 142 | path = VNCameraScanner; 143 | sourceTree = ""; 144 | }; 145 | /* End PBXGroup section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | AB6229AE1E9CF37A00CB9510 /* VNImageScanner */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = AB6229DA1E9CF37B00CB9510 /* Build configuration list for PBXNativeTarget "VNImageScanner" */; 151 | buildPhases = ( 152 | AB6229AB1E9CF37A00CB9510 /* Sources */, 153 | AB6229AC1E9CF37A00CB9510 /* Frameworks */, 154 | AB6229AD1E9CF37A00CB9510 /* Resources */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | ); 160 | name = VNImageScanner; 161 | productName = VNImageScanner; 162 | productReference = AB6229AF1E9CF37A00CB9510 /* VNImageScanner.app */; 163 | productType = "com.apple.product-type.application"; 164 | }; 165 | AB6229C51E9CF37B00CB9510 /* VNImageScannerTests */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = AB6229DD1E9CF37B00CB9510 /* Build configuration list for PBXNativeTarget "VNImageScannerTests" */; 168 | buildPhases = ( 169 | AB6229C21E9CF37B00CB9510 /* Sources */, 170 | AB6229C31E9CF37B00CB9510 /* Frameworks */, 171 | AB6229C41E9CF37B00CB9510 /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | AB6229C81E9CF37B00CB9510 /* PBXTargetDependency */, 177 | ); 178 | name = VNImageScannerTests; 179 | productName = VNImageScannerTests; 180 | productReference = AB6229C61E9CF37B00CB9510 /* VNImageScannerTests.xctest */; 181 | productType = "com.apple.product-type.bundle.unit-test"; 182 | }; 183 | AB6229D01E9CF37B00CB9510 /* VNImageScannerUITests */ = { 184 | isa = PBXNativeTarget; 185 | buildConfigurationList = AB6229E01E9CF37B00CB9510 /* Build configuration list for PBXNativeTarget "VNImageScannerUITests" */; 186 | buildPhases = ( 187 | AB6229CD1E9CF37B00CB9510 /* Sources */, 188 | AB6229CE1E9CF37B00CB9510 /* Frameworks */, 189 | AB6229CF1E9CF37B00CB9510 /* Resources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | AB6229D31E9CF37B00CB9510 /* PBXTargetDependency */, 195 | ); 196 | name = VNImageScannerUITests; 197 | productName = VNImageScannerUITests; 198 | productReference = AB6229D11E9CF37B00CB9510 /* VNImageScannerUITests.xctest */; 199 | productType = "com.apple.product-type.bundle.ui-testing"; 200 | }; 201 | /* End PBXNativeTarget section */ 202 | 203 | /* Begin PBXProject section */ 204 | AB6229A71E9CF37A00CB9510 /* Project object */ = { 205 | isa = PBXProject; 206 | attributes = { 207 | LastSwiftUpdateCheck = 0820; 208 | LastUpgradeCheck = 1250; 209 | ORGANIZATIONNAME = Varun; 210 | TargetAttributes = { 211 | AB6229AE1E9CF37A00CB9510 = { 212 | CreatedOnToolsVersion = 8.2; 213 | DevelopmentTeam = 2658J76W2S; 214 | LastSwiftMigration = 1250; 215 | ProvisioningStyle = Automatic; 216 | }; 217 | AB6229C51E9CF37B00CB9510 = { 218 | CreatedOnToolsVersion = 8.2; 219 | DevelopmentTeam = 2658J76W2S; 220 | LastSwiftMigration = 1250; 221 | ProvisioningStyle = Automatic; 222 | TestTargetID = AB6229AE1E9CF37A00CB9510; 223 | }; 224 | AB6229D01E9CF37B00CB9510 = { 225 | CreatedOnToolsVersion = 8.2; 226 | DevelopmentTeam = 2658J76W2S; 227 | LastSwiftMigration = 1250; 228 | ProvisioningStyle = Automatic; 229 | TestTargetID = AB6229AE1E9CF37A00CB9510; 230 | }; 231 | }; 232 | }; 233 | buildConfigurationList = AB6229AA1E9CF37A00CB9510 /* Build configuration list for PBXProject "VNImageScanner" */; 234 | compatibilityVersion = "Xcode 3.2"; 235 | developmentRegion = en; 236 | hasScannedForEncodings = 0; 237 | knownRegions = ( 238 | en, 239 | Base, 240 | ); 241 | mainGroup = AB6229A61E9CF37A00CB9510; 242 | productRefGroup = AB6229B01E9CF37A00CB9510 /* Products */; 243 | projectDirPath = ""; 244 | projectRoot = ""; 245 | targets = ( 246 | AB6229AE1E9CF37A00CB9510 /* VNImageScanner */, 247 | AB6229C51E9CF37B00CB9510 /* VNImageScannerTests */, 248 | AB6229D01E9CF37B00CB9510 /* VNImageScannerUITests */, 249 | ); 250 | }; 251 | /* End PBXProject section */ 252 | 253 | /* Begin PBXResourcesBuildPhase section */ 254 | AB6229AD1E9CF37A00CB9510 /* Resources */ = { 255 | isa = PBXResourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | AB6229C01E9CF37A00CB9510 /* LaunchScreen.storyboard in Resources */, 259 | AB6229BD1E9CF37A00CB9510 /* Assets.xcassets in Resources */, 260 | AB84312A1E9E40EF002D0CA1 /* VNCameraScanner.xib in Resources */, 261 | AB84312D1E9E4C02002D0CA1 /* Main.storyboard in Resources */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | AB6229C41E9CF37B00CB9510 /* Resources */ = { 266 | isa = PBXResourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | AB6229CF1E9CF37B00CB9510 /* Resources */ = { 273 | isa = PBXResourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXResourcesBuildPhase section */ 280 | 281 | /* Begin PBXSourcesBuildPhase section */ 282 | AB6229AB1E9CF37A00CB9510 /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | AB6229B51E9CF37A00CB9510 /* ViewController.swift in Sources */, 287 | AB6229BB1E9CF37A00CB9510 /* VNImageScanner.xcdatamodeld in Sources */, 288 | AB6229B31E9CF37A00CB9510 /* AppDelegate.swift in Sources */, 289 | AB6229E51E9CFD9000CB9510 /* VNCameraScanner.swift in Sources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | AB6229C21E9CF37B00CB9510 /* Sources */ = { 294 | isa = PBXSourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | AB6229CB1E9CF37B00CB9510 /* VNImageScannerTests.swift in Sources */, 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | AB6229CD1E9CF37B00CB9510 /* Sources */ = { 302 | isa = PBXSourcesBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | AB6229D61E9CF37B00CB9510 /* VNImageScannerUITests.swift in Sources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | /* End PBXSourcesBuildPhase section */ 310 | 311 | /* Begin PBXTargetDependency section */ 312 | AB6229C81E9CF37B00CB9510 /* PBXTargetDependency */ = { 313 | isa = PBXTargetDependency; 314 | target = AB6229AE1E9CF37A00CB9510 /* VNImageScanner */; 315 | targetProxy = AB6229C71E9CF37B00CB9510 /* PBXContainerItemProxy */; 316 | }; 317 | AB6229D31E9CF37B00CB9510 /* PBXTargetDependency */ = { 318 | isa = PBXTargetDependency; 319 | target = AB6229AE1E9CF37A00CB9510 /* VNImageScanner */; 320 | targetProxy = AB6229D21E9CF37B00CB9510 /* PBXContainerItemProxy */; 321 | }; 322 | /* End PBXTargetDependency section */ 323 | 324 | /* Begin PBXVariantGroup section */ 325 | AB6229BE1E9CF37A00CB9510 /* LaunchScreen.storyboard */ = { 326 | isa = PBXVariantGroup; 327 | children = ( 328 | AB6229BF1E9CF37A00CB9510 /* Base */, 329 | ); 330 | name = LaunchScreen.storyboard; 331 | sourceTree = ""; 332 | }; 333 | AB84312B1E9E4C02002D0CA1 /* Main.storyboard */ = { 334 | isa = PBXVariantGroup; 335 | children = ( 336 | AB84312C1E9E4C02002D0CA1 /* Base */, 337 | ); 338 | name = Main.storyboard; 339 | sourceTree = ""; 340 | }; 341 | /* End PBXVariantGroup section */ 342 | 343 | /* Begin XCBuildConfiguration section */ 344 | AB6229D81E9CF37B00CB9510 /* Debug */ = { 345 | isa = XCBuildConfiguration; 346 | buildSettings = { 347 | ALWAYS_SEARCH_USER_PATHS = NO; 348 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 349 | CLANG_ANALYZER_NONNULL = YES; 350 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 351 | CLANG_CXX_LIBRARY = "libc++"; 352 | CLANG_ENABLE_MODULES = YES; 353 | CLANG_ENABLE_OBJC_ARC = YES; 354 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 355 | CLANG_WARN_BOOL_CONVERSION = YES; 356 | CLANG_WARN_COMMA = YES; 357 | CLANG_WARN_CONSTANT_CONVERSION = YES; 358 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 359 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 360 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 361 | CLANG_WARN_EMPTY_BODY = YES; 362 | CLANG_WARN_ENUM_CONVERSION = YES; 363 | CLANG_WARN_INFINITE_RECURSION = YES; 364 | CLANG_WARN_INT_CONVERSION = YES; 365 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 366 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 367 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 368 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 369 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 370 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 371 | CLANG_WARN_STRICT_PROTOTYPES = YES; 372 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 373 | CLANG_WARN_UNREACHABLE_CODE = YES; 374 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 375 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 376 | COPY_PHASE_STRIP = NO; 377 | DEBUG_INFORMATION_FORMAT = dwarf; 378 | ENABLE_STRICT_OBJC_MSGSEND = YES; 379 | ENABLE_TESTABILITY = YES; 380 | GCC_C_LANGUAGE_STANDARD = gnu99; 381 | GCC_DYNAMIC_NO_PIC = NO; 382 | GCC_NO_COMMON_BLOCKS = YES; 383 | GCC_OPTIMIZATION_LEVEL = 0; 384 | GCC_PREPROCESSOR_DEFINITIONS = ( 385 | "DEBUG=1", 386 | "$(inherited)", 387 | ); 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 395 | MTL_ENABLE_DEBUG_INFO = YES; 396 | ONLY_ACTIVE_ARCH = YES; 397 | SDKROOT = iphoneos; 398 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 399 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | }; 402 | name = Debug; 403 | }; 404 | AB6229D91E9CF37B00CB9510 /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ALWAYS_SEARCH_USER_PATHS = NO; 408 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 409 | CLANG_ANALYZER_NONNULL = YES; 410 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 411 | CLANG_CXX_LIBRARY = "libc++"; 412 | CLANG_ENABLE_MODULES = YES; 413 | CLANG_ENABLE_OBJC_ARC = YES; 414 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 415 | CLANG_WARN_BOOL_CONVERSION = YES; 416 | CLANG_WARN_COMMA = YES; 417 | CLANG_WARN_CONSTANT_CONVERSION = YES; 418 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 419 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 420 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 421 | CLANG_WARN_EMPTY_BODY = YES; 422 | CLANG_WARN_ENUM_CONVERSION = YES; 423 | CLANG_WARN_INFINITE_RECURSION = YES; 424 | CLANG_WARN_INT_CONVERSION = YES; 425 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 427 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 428 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 429 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 430 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 431 | CLANG_WARN_STRICT_PROTOTYPES = YES; 432 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 433 | CLANG_WARN_UNREACHABLE_CODE = YES; 434 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 435 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 436 | COPY_PHASE_STRIP = NO; 437 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 438 | ENABLE_NS_ASSERTIONS = NO; 439 | ENABLE_STRICT_OBJC_MSGSEND = YES; 440 | GCC_C_LANGUAGE_STANDARD = gnu99; 441 | GCC_NO_COMMON_BLOCKS = YES; 442 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 443 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 444 | GCC_WARN_UNDECLARED_SELECTOR = YES; 445 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 446 | GCC_WARN_UNUSED_FUNCTION = YES; 447 | GCC_WARN_UNUSED_VARIABLE = YES; 448 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 449 | MTL_ENABLE_DEBUG_INFO = NO; 450 | SDKROOT = iphoneos; 451 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 452 | TARGETED_DEVICE_FAMILY = "1,2"; 453 | VALIDATE_PRODUCT = YES; 454 | }; 455 | name = Release; 456 | }; 457 | AB6229DB1E9CF37B00CB9510 /* Debug */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 461 | DEVELOPMENT_TEAM = 2658J76W2S; 462 | INFOPLIST_FILE = VNImageScanner/Info.plist; 463 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 464 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 465 | PRODUCT_BUNDLE_IDENTIFIER = com.varun.demo; 466 | PRODUCT_NAME = "$(TARGET_NAME)"; 467 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 468 | SWIFT_VERSION = 5.0; 469 | }; 470 | name = Debug; 471 | }; 472 | AB6229DC1E9CF37B00CB9510 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 476 | DEVELOPMENT_TEAM = 2658J76W2S; 477 | INFOPLIST_FILE = VNImageScanner/Info.plist; 478 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 480 | PRODUCT_BUNDLE_IDENTIFIER = com.varun.demo; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 483 | SWIFT_VERSION = 5.0; 484 | }; 485 | name = Release; 486 | }; 487 | AB6229DE1E9CF37B00CB9510 /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 491 | BUNDLE_LOADER = "$(TEST_HOST)"; 492 | DEVELOPMENT_TEAM = 2658J76W2S; 493 | INFOPLIST_FILE = VNImageScannerTests/Info.plist; 494 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 495 | PRODUCT_BUNDLE_IDENTIFIER = com.varunnaharia.VNImageScannerTests; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 498 | SWIFT_VERSION = 5.0; 499 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VNImageScanner.app/VNImageScanner"; 500 | }; 501 | name = Debug; 502 | }; 503 | AB6229DF1E9CF37B00CB9510 /* Release */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 507 | BUNDLE_LOADER = "$(TEST_HOST)"; 508 | DEVELOPMENT_TEAM = 2658J76W2S; 509 | INFOPLIST_FILE = VNImageScannerTests/Info.plist; 510 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 511 | PRODUCT_BUNDLE_IDENTIFIER = com.varunnaharia.VNImageScannerTests; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 514 | SWIFT_VERSION = 5.0; 515 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/VNImageScanner.app/VNImageScanner"; 516 | }; 517 | name = Release; 518 | }; 519 | AB6229E11E9CF37B00CB9510 /* Debug */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 523 | DEVELOPMENT_TEAM = 2658J76W2S; 524 | INFOPLIST_FILE = VNImageScannerUITests/Info.plist; 525 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 526 | PRODUCT_BUNDLE_IDENTIFIER = com.varunnaharia.VNImageScannerUITests; 527 | PRODUCT_NAME = "$(TARGET_NAME)"; 528 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 529 | SWIFT_VERSION = 5.0; 530 | TEST_TARGET_NAME = VNImageScanner; 531 | }; 532 | name = Debug; 533 | }; 534 | AB6229E21E9CF37B00CB9510 /* Release */ = { 535 | isa = XCBuildConfiguration; 536 | buildSettings = { 537 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 538 | DEVELOPMENT_TEAM = 2658J76W2S; 539 | INFOPLIST_FILE = VNImageScannerUITests/Info.plist; 540 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 541 | PRODUCT_BUNDLE_IDENTIFIER = com.varunnaharia.VNImageScannerUITests; 542 | PRODUCT_NAME = "$(TARGET_NAME)"; 543 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 544 | SWIFT_VERSION = 5.0; 545 | TEST_TARGET_NAME = VNImageScanner; 546 | }; 547 | name = Release; 548 | }; 549 | /* End XCBuildConfiguration section */ 550 | 551 | /* Begin XCConfigurationList section */ 552 | AB6229AA1E9CF37A00CB9510 /* Build configuration list for PBXProject "VNImageScanner" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | AB6229D81E9CF37B00CB9510 /* Debug */, 556 | AB6229D91E9CF37B00CB9510 /* Release */, 557 | ); 558 | defaultConfigurationIsVisible = 0; 559 | defaultConfigurationName = Release; 560 | }; 561 | AB6229DA1E9CF37B00CB9510 /* Build configuration list for PBXNativeTarget "VNImageScanner" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | AB6229DB1E9CF37B00CB9510 /* Debug */, 565 | AB6229DC1E9CF37B00CB9510 /* Release */, 566 | ); 567 | defaultConfigurationIsVisible = 0; 568 | defaultConfigurationName = Release; 569 | }; 570 | AB6229DD1E9CF37B00CB9510 /* Build configuration list for PBXNativeTarget "VNImageScannerTests" */ = { 571 | isa = XCConfigurationList; 572 | buildConfigurations = ( 573 | AB6229DE1E9CF37B00CB9510 /* Debug */, 574 | AB6229DF1E9CF37B00CB9510 /* Release */, 575 | ); 576 | defaultConfigurationIsVisible = 0; 577 | defaultConfigurationName = Release; 578 | }; 579 | AB6229E01E9CF37B00CB9510 /* Build configuration list for PBXNativeTarget "VNImageScannerUITests" */ = { 580 | isa = XCConfigurationList; 581 | buildConfigurations = ( 582 | AB6229E11E9CF37B00CB9510 /* Debug */, 583 | AB6229E21E9CF37B00CB9510 /* Release */, 584 | ); 585 | defaultConfigurationIsVisible = 0; 586 | defaultConfigurationName = Release; 587 | }; 588 | /* End XCConfigurationList section */ 589 | 590 | /* Begin XCVersionGroup section */ 591 | AB6229B91E9CF37A00CB9510 /* VNImageScanner.xcdatamodeld */ = { 592 | isa = XCVersionGroup; 593 | children = ( 594 | AB6229BA1E9CF37A00CB9510 /* VNImageScanner.xcdatamodel */, 595 | ); 596 | currentVersion = AB6229BA1E9CF37A00CB9510 /* VNImageScanner.xcdatamodel */; 597 | path = VNImageScanner.xcdatamodeld; 598 | sourceTree = ""; 599 | versionGroupType = wrapper.xcdatamodel; 600 | }; 601 | /* End XCVersionGroup section */ 602 | }; 603 | rootObject = AB6229A71E9CF37A00CB9510 /* Project object */; 604 | } 605 | -------------------------------------------------------------------------------- /VNImageScanner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VNImageScanner.xcodeproj/xcuserdata/varunnaharia.xcuserdatad/xcschemes/VNImageScanner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 44 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 76 | 78 | 84 | 85 | 86 | 87 | 88 | 89 | 95 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /VNImageScanner.xcodeproj/xcuserdata/varunnaharia.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | VNImageScanner.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | AB6229AE1E9CF37A00CB9510 16 | 17 | primary 18 | 19 | 20 | AB6229C51E9CF37B00CB9510 21 | 22 | primary 23 | 24 | 25 | AB6229D01E9CF37B00CB9510 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /VNImageScanner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // VNImageScanner 4 | // 5 | // Created by Varun Naharia on 11/04/17. 6 | // Copyright © 2017 Varun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 19 | // Override point for customization after application launch. 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // 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. 25 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // 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. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | // Saves changes in the application's managed object context before the application terminates. 44 | self.saveContext() 45 | } 46 | 47 | // MARK: - Core Data stack 48 | 49 | lazy var persistentContainer: NSPersistentContainer = { 50 | /* 51 | The persistent container for the application. This implementation 52 | creates and returns a container, having loaded the store for the 53 | application to it. This property is optional since there are legitimate 54 | error conditions that could cause the creation of the store to fail. 55 | */ 56 | let container = NSPersistentContainer(name: "VNImageScanner") 57 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 58 | if let error = error as NSError? { 59 | // Replace this implementation with code to handle the error appropriately. 60 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 61 | 62 | /* 63 | Typical reasons for an error here include: 64 | * The parent directory does not exist, cannot be created, or disallows writing. 65 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 66 | * The device is out of space. 67 | * The store could not be migrated to the current model version. 68 | Check the error message to determine what the actual problem was. 69 | */ 70 | fatalError("Unresolved error \(error), \(error.userInfo)") 71 | } 72 | }) 73 | return container 74 | }() 75 | 76 | // MARK: - Core Data Saving support 77 | 78 | func saveContext () { 79 | let context = persistentContainer.viewContext 80 | if context.hasChanges { 81 | do { 82 | try context.save() 83 | } catch { 84 | // Replace this implementation with code to handle the error appropriately. 85 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 86 | let nserror = error as NSError 87 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 88 | } 89 | } 90 | } 91 | 92 | } 93 | 94 | -------------------------------------------------------------------------------- /VNImageScanner/Assets.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 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /VNImageScanner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VNImageScanner/Assets.xcassets/capture_button.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "capture_button@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /VNImageScanner/Assets.xcassets/capture_button.imageset/capture_button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varun-naharia/VNImageScanner/d8cd0704433633cbcb0dd73b10550424c5d60acb/VNImageScanner/Assets.xcassets/capture_button.imageset/capture_button@2x.png -------------------------------------------------------------------------------- /VNImageScanner/Assets.xcassets/focusIndicator.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "focusIndicator.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "focusIndicator@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VNImageScanner/Assets.xcassets/focusIndicator.imageset/focusIndicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varun-naharia/VNImageScanner/d8cd0704433633cbcb0dd73b10550424c5d60acb/VNImageScanner/Assets.xcassets/focusIndicator.imageset/focusIndicator.png -------------------------------------------------------------------------------- /VNImageScanner/Assets.xcassets/focusIndicator.imageset/focusIndicator@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varun-naharia/VNImageScanner/d8cd0704433633cbcb0dd73b10550424c5d60acb/VNImageScanner/Assets.xcassets/focusIndicator.imageset/focusIndicator@2x.png -------------------------------------------------------------------------------- /VNImageScanner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /VNImageScanner/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 | 40 | 47 | 58 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 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 | 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 | -------------------------------------------------------------------------------- /VNImageScanner/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | $(PRODUCT_NAME) uses camera 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /VNImageScanner/VNCameraScanner/VNCameraScanner.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VNCameraScanner.swift 3 | // VNImageScanner 4 | // 5 | // Created by Varun Naharia on 11/04/17. 6 | // Copyright © 2017 Varun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import CoreMedia 12 | import CoreVideo 13 | import QuartzCore 14 | import CoreImage 15 | import ImageIO 16 | import MobileCoreServices 17 | import GLKit 18 | import OpenGLES 19 | 20 | enum VNCameraViewType : Int { 21 | case blackAndWhite 22 | case normal 23 | } 24 | 25 | class VNRectangleFeature: CIFeature { 26 | open var topLeft = CGPoint.zero 27 | open var topRight = CGPoint.zero 28 | open var bottomRight = CGPoint.zero 29 | open var bottomLeft = CGPoint.zero 30 | 31 | 32 | class func setValue(topLeft:CGPoint, topRight:CGPoint, bottomLeft:CGPoint, bottomRight:CGPoint) -> VNRectangleFeature { 33 | let obj:VNRectangleFeature = VNRectangleFeature() 34 | obj.topLeft = topLeft 35 | obj.topRight = topRight 36 | obj.bottomLeft = bottomLeft 37 | obj.bottomRight = bottomLeft 38 | return obj 39 | } 40 | } 41 | class VNCameraScanner:UIView, AVCaptureVideoDataOutputSampleBufferDelegate { 42 | var captureSession: AVCaptureSession? 43 | var captureDevice: AVCaptureDevice? 44 | var context: EAGLContext? 45 | var stillImageOutput: AVCaptureStillImageOutput? 46 | var isForceStop: Bool = false 47 | private var _intrinsicContentSize:CGSize = CGSize(width: 0, height: 0) 48 | override var intrinsicContentSize: CGSize { 49 | get { 50 | //... 51 | return _intrinsicContentSize 52 | } 53 | set { 54 | 55 | _intrinsicContentSize = newValue 56 | } 57 | 58 | } 59 | var coreImageContext: CIContext? 60 | var renderBuffer = GLuint() 61 | var glkView: GLKView? 62 | var isStopped: Bool = false 63 | var imageDedectionConfidence: CGFloat = 0.0 64 | var borderDetectTimeKeeper: Timer? 65 | var borderDetectFrame: Bool = false 66 | var borderDetectLastRectangleFeature: VNRectangleFeature? 67 | var isCapturing: Bool = false 68 | var captureQueue:DispatchQueue! 69 | var cameraViewType:VNCameraViewType! 70 | var isEnableBorderDetection: Bool = false 71 | var isEnableTorch: Bool = false 72 | 73 | override func awakeFromNib() { 74 | super.awakeFromNib() 75 | NotificationCenter.default.addObserver(self, selector: #selector(self._backgroundMode), name: UIApplication.willResignActiveNotification, object: nil) 76 | NotificationCenter.default.addObserver(self, selector: #selector(self._foregroundMode), name: UIApplication.didBecomeActiveNotification, object: nil) 77 | captureQueue = DispatchQueue(label: "com.instapdf.AVCameraCaptureQueue") 78 | } 79 | 80 | @objc func _backgroundMode() { 81 | isForceStop = true 82 | } 83 | 84 | @objc func _foregroundMode() { 85 | isForceStop = false 86 | } 87 | deinit { 88 | NotificationCenter.default.removeObserver(self) 89 | } 90 | 91 | func createGLKView() { 92 | if (context != nil) { 93 | return 94 | } 95 | context = EAGLContext(api: EAGLRenderingAPI.openGLES2) 96 | let view = GLKView(frame: bounds) 97 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 98 | view.translatesAutoresizingMaskIntoConstraints = true 99 | view.context = context! 100 | view.contentScaleFactor = 1.0 101 | view.drawableDepthFormat = GLKViewDrawableDepthFormat.format24 102 | insertSubview(view, at: 0) 103 | glkView = view 104 | coreImageContext = CIContext(eaglContext: context!, options: convertToOptionalCIContextOptionDictionary([convertFromCIContextOption(CIContextOption.workingColorSpace): NSNull(), convertFromCIContextOption(CIContextOption.useSoftwareRenderer): (false)])) 105 | } 106 | func setupCameraView() { 107 | createGLKView() 108 | let possibleDevices: [Any] = AVCaptureDevice.devices(for: AVMediaType.video) 109 | let device: AVCaptureDevice? = possibleDevices.first as! AVCaptureDevice? 110 | if device == nil { 111 | return 112 | } 113 | imageDedectionConfidence = 0.0 114 | let session = AVCaptureSession() 115 | captureSession = session 116 | session.beginConfiguration() 117 | captureDevice = device 118 | var error: Error? = nil 119 | let input = try? AVCaptureDeviceInput(device: device!) 120 | session.sessionPreset = AVCaptureSession.Preset.photo 121 | session.addInput(input!) 122 | let dataOutput = AVCaptureVideoDataOutput() 123 | dataOutput.alwaysDiscardsLateVideoFrames = true 124 | dataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as AnyHashable as! String: kCVPixelFormatType_32BGRA] 125 | dataOutput.setSampleBufferDelegate(self, queue: captureQueue) 126 | session.addOutput(dataOutput) 127 | 128 | stillImageOutput = AVCaptureStillImageOutput() 129 | session.addOutput(stillImageOutput!) 130 | let connection: AVCaptureConnection? = dataOutput.connections.first 131 | connection?.videoOrientation = .portrait 132 | if (device?.isFlashAvailable)! { 133 | do{ 134 | try device?.lockForConfiguration() 135 | } 136 | catch 137 | { 138 | 139 | } 140 | device?.flashMode = .off 141 | device?.unlockForConfiguration() 142 | if (device?.isFocusModeSupported(.continuousAutoFocus))! { 143 | do{ 144 | try device?.lockForConfiguration() 145 | } 146 | catch 147 | { 148 | 149 | } 150 | device?.focusMode = .continuousAutoFocus 151 | device?.unlockForConfiguration() 152 | } 153 | } 154 | session.commitConfiguration() 155 | } 156 | 157 | func setCameraViewType(_ cameraViewType: VNCameraViewType) { 158 | let effect = UIBlurEffect(style: .dark) 159 | let viewWithBlurredBackground = UIVisualEffectView(effect: effect) 160 | viewWithBlurredBackground.frame = bounds 161 | insertSubview(viewWithBlurredBackground, aboveSubview: glkView!) 162 | self.cameraViewType = cameraViewType 163 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: {() -> Void in 164 | viewWithBlurredBackground.removeFromSuperview() 165 | }) 166 | } 167 | 168 | func captureOutput(_ captureOutput: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 169 | 170 | if isForceStop { 171 | return 172 | } 173 | if isStopped || isCapturing || !CMSampleBufferIsValid(sampleBuffer) { 174 | return 175 | } 176 | let pixelBuffer: CVPixelBuffer? = CMSampleBufferGetImageBuffer(sampleBuffer)! 177 | var image = CIImage(cvPixelBuffer: pixelBuffer!) 178 | if cameraViewType != VNCameraViewType.normal { 179 | image = filteredImageUsingEnhanceFilter(on: image) 180 | } 181 | else { 182 | image = filteredImageUsingContrastFilter(on: image) 183 | } 184 | if isEnableBorderDetection { 185 | if borderDetectFrame { 186 | borderDetectLastRectangleFeature = biggestRectangle(inRectangles: (highAccuracyRectangleDetector()?.features(in: image))!) 187 | borderDetectFrame = false 188 | } 189 | if (borderDetectLastRectangleFeature?.bottomLeft != nil) { 190 | imageDedectionConfidence += 0.5 191 | image = drawHighlightOverlay(forPoints: image, topLeft: (borderDetectLastRectangleFeature?.topLeft)!, topRight: (borderDetectLastRectangleFeature?.topRight)!, bottomLeft: (borderDetectLastRectangleFeature?.bottomLeft)!, bottomRight: (borderDetectLastRectangleFeature?.bottomRight)!) 192 | } 193 | else { 194 | imageDedectionConfidence = 0.0 195 | } 196 | } 197 | if ((self.context != nil) && (coreImageContext != nil)) 198 | { 199 | if(context != EAGLContext.current()) 200 | { 201 | EAGLContext.setCurrent(context) 202 | } 203 | glkView?.bindDrawable() 204 | DispatchQueue.main.sync { 205 | coreImageContext?.draw(image, in: self.bounds, from: self.cropRect(forPreviewImage: image)) 206 | } 207 | 208 | glkView?.display() 209 | 210 | if(intrinsicContentSize.width != image.extent.size.width) { 211 | self.intrinsicContentSize = image.extent.size; 212 | DispatchQueue.main.async { 213 | self.invalidateIntrinsicContentSize() 214 | } 215 | } 216 | 217 | image = CIImage(); 218 | } 219 | } 220 | 221 | func filteredImageUsingEnhanceFilter(on image: CIImage) -> CIImage { 222 | return (CIFilter(name: "CIColorControls", parameters: [kCIInputImageKey:image, "inputBrightness": NSNumber(value: 0.0), "inputContrast":NSNumber(value: 1.14), "inputSaturation": NSNumber(value: 0.0)])?.outputImage)! 223 | } 224 | 225 | func filteredImageUsingContrastFilter(on image: CIImage) -> CIImage { 226 | return CIFilter(name: "CIColorControls", parameters: ["inputContrast": (1.1), kCIInputImageKey: image])!.outputImage! 227 | } 228 | 229 | func _biggestRectangle(inRectangles rectangles: [Any]) -> CIRectangleFeature? { 230 | if !(rectangles.count > 0){ 231 | return nil 232 | } 233 | var halfPerimiterValue: Float = 0 234 | var biggestRectangle: CIRectangleFeature = rectangles.first as! CIRectangleFeature 235 | for rect: CIRectangleFeature in rectangles as! [CIRectangleFeature] { 236 | let p1: CGPoint = rect.topLeft 237 | let p2: CGPoint = rect.topRight 238 | let width: CGFloat = CGFloat(hypotf(Float(p1.x) - Float(p2.x), Float(p1.y) - Float(p2.y))) 239 | let p3: CGPoint = rect.topLeft 240 | let p4: CGPoint = rect.bottomLeft 241 | let height: CGFloat = CGFloat(hypotf(Float(p3.x) - Float(p4.x), Float(p3.y) - Float(p4.y))) 242 | let currentHalfPerimiterValue: CGFloat = height + width 243 | if halfPerimiterValue < Float(currentHalfPerimiterValue) { 244 | halfPerimiterValue = Float(currentHalfPerimiterValue) 245 | biggestRectangle = rect 246 | } 247 | } 248 | return biggestRectangle 249 | } 250 | 251 | func biggestRectangle(inRectangles rectangles: [Any]) -> VNRectangleFeature? { 252 | let rectangleFeature: CIRectangleFeature? = _biggestRectangle(inRectangles: rectangles) 253 | if rectangleFeature == nil { 254 | return nil 255 | } 256 | // Credit: http://stackoverflow.com/a/20399468/1091044 257 | // http://stackoverflow.com/questions/42474408/ 258 | let points = [ 259 | rectangleFeature?.topLeft, 260 | rectangleFeature?.topRight, 261 | rectangleFeature?.bottomLeft, 262 | rectangleFeature?.bottomRight 263 | ] 264 | 265 | var minimum = points[0] 266 | var maximum = points[0] 267 | for point in points { 268 | let minx = min((minimum?.x)!, (point?.x)!) 269 | let miny = min((minimum?.y)!, (point?.y)!) 270 | let maxx = max((maximum?.x)!, (point?.x)!) 271 | let maxy = max((maximum?.y)!, (point?.y)!) 272 | minimum?.x = minx 273 | minimum?.y = miny 274 | maximum?.x = maxx 275 | maximum?.y = maxy 276 | } 277 | let center = CGPoint(x: ((minimum?.x)! + (maximum?.x)!) / 2, y: ((minimum?.y)! + (maximum?.y)!) / 2) 278 | let angle = { (point: CGPoint) -> CGFloat in 279 | let theta = atan2(point.y - center.y, point.x - center.x) 280 | return fmod(.pi * 3.0 / 4.0 + theta, 2 * .pi) 281 | } 282 | let sortedPoints = points.sorted{angle($0!) < angle($1!)} 283 | let rectangleFeatureMutable = VNRectangleFeature() 284 | rectangleFeatureMutable.topLeft = sortedPoints[3]! 285 | rectangleFeatureMutable.topRight = sortedPoints[2]! 286 | rectangleFeatureMutable.bottomRight = sortedPoints[1]! 287 | rectangleFeatureMutable.bottomLeft = sortedPoints[0]! 288 | return rectangleFeatureMutable 289 | } 290 | 291 | func highAccuracyRectangleDetector() -> CIDetector? { 292 | var detector: CIDetector? = nil 293 | detector = CIDetector.init(ofType: CIDetectorTypeRectangle, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) 294 | return detector! 295 | } 296 | 297 | func drawHighlightOverlay(forPoints image: CIImage, topLeft: CGPoint, topRight: CGPoint, bottomLeft: CGPoint, bottomRight: CGPoint) -> CIImage { 298 | var overlay = CIImage(color: CIColor(red: CGFloat(1), green: CGFloat(0), blue: CGFloat(0), alpha: CGFloat(0.6))) 299 | overlay = overlay.cropped(to: image.extent) 300 | overlay = overlay.applyingFilter("CIPerspectiveTransformWithExtent", parameters: ["inputExtent": CIVector(cgRect: image.extent), "inputTopLeft": CIVector(cgPoint: topLeft), "inputTopRight": CIVector(cgPoint:topRight), "inputBottomLeft": CIVector(cgPoint:bottomLeft), "inputBottomRight": CIVector(cgPoint:bottomRight)]) //applyingFilter("CIPerspectiveTransformWithExtent", withInputParameters: ["inputExtent": CIVector(cgRect: image.extent()), "inputTopLeft": CIVector(topLeft), "inputTopRight": CIVector(topRight), "inputBottomLeft": CIVector(bottomLeft), "inputBottomRight": CIVector(bottomRight)]) 301 | return overlay.composited(over: image) 302 | } 303 | 304 | func cropRect(forPreviewImage image: CIImage) -> CGRect { 305 | var cropWidth: CGFloat = image.extent.size.width 306 | var cropHeight: CGFloat = image.extent.size.height 307 | if image.extent.size.width > image.extent.size.height { 308 | cropWidth = image.extent.size.width 309 | cropHeight = cropWidth * bounds.size.height / bounds.size.width 310 | } 311 | else if image.extent.size.width < image.extent.size.height { 312 | cropHeight = image.extent.size.height 313 | cropWidth = cropHeight * bounds.size.width / bounds.size.height 314 | } 315 | 316 | return image.extent.insetBy(dx: CGFloat((image.extent.size.width - cropWidth) / 2), dy: CGFloat((image.extent.size.height - cropHeight) / 2)) 317 | } 318 | 319 | 320 | func rectangleDetectionConfidenceHighEnough(confidence:Float) -> Bool { 321 | return (confidence > 1.0) 322 | } 323 | 324 | func start() { 325 | isStopped = false 326 | captureSession?.startRunning() 327 | borderDetectTimeKeeper = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(VNCameraScanner.enableBorderDetectFrame), userInfo: nil, repeats: true) 328 | hideGLKView(false) 329 | } 330 | 331 | func stop() { 332 | isStopped = true 333 | captureSession?.stopRunning() 334 | borderDetectTimeKeeper?.invalidate() 335 | hideGLKView(true) 336 | } 337 | 338 | @objc func enableBorderDetectFrame() { 339 | borderDetectFrame = true 340 | } 341 | 342 | func hideGLKView(_ hidden: Bool) { 343 | UIView.animate(withDuration: 0.1, animations: {() -> Void in 344 | self.glkView?.alpha = (hidden) ? 0.0 : 1.0 345 | }, completion: {(_ finished: Bool) -> Void in 346 | if !finished { 347 | return 348 | } 349 | }) 350 | } 351 | 352 | func focus(at point: CGPoint, completionHandler: @escaping () -> Void) { 353 | let device: AVCaptureDevice? = captureDevice 354 | var pointOfInterest = CGPoint.zero 355 | let frameSize: CGSize = bounds.size 356 | pointOfInterest = CGPoint(x: CGFloat(point.y / frameSize.height), y: CGFloat(1.0 - (point.x / frameSize.width))) 357 | if (device?.isFocusPointOfInterestSupported)! && (device?.isFocusModeSupported(.autoFocus))! { 358 | do{ 359 | try device?.lockForConfiguration() 360 | if (device?.isFocusModeSupported(.continuousAutoFocus))! { 361 | device?.focusMode = .continuousAutoFocus 362 | device?.focusPointOfInterest = pointOfInterest 363 | } 364 | if (device?.isExposurePointOfInterestSupported)! && (device?.isExposureModeSupported(.continuousAutoExposure))! { 365 | device?.exposurePointOfInterest = pointOfInterest 366 | device?.exposureMode = .continuousAutoExposure 367 | completionHandler() 368 | } 369 | device?.unlockForConfiguration() 370 | } 371 | catch 372 | { 373 | 374 | } 375 | } 376 | else { 377 | completionHandler() 378 | } 379 | } 380 | 381 | 382 | func captureImage(withCompletionHander completionHandler: @escaping (_ imageFilePath: String) -> Void) { 383 | captureQueue.suspend() 384 | var videoConnection: AVCaptureConnection? = nil 385 | for connection: AVCaptureConnection in stillImageOutput?.connections as! [AVCaptureConnection] { 386 | for port: AVCaptureInput.Port in connection.inputPorts { 387 | if port.mediaType == AVMediaType.video{ 388 | videoConnection = connection 389 | break 390 | } 391 | } 392 | if videoConnection != nil { 393 | break 394 | } 395 | } 396 | weak var weakSelf = self 397 | 398 | stillImageOutput?.captureStillImageAsynchronously(from: videoConnection!, completionHandler: {(_ imageSampleBuffer: CMSampleBuffer?, _ error: Error?) -> Void in 399 | if error != nil { 400 | self.captureQueue.resume() 401 | return 402 | } 403 | let filePath: String = NSTemporaryDirectory().stringByAppendingPathComponent(path: "vn_img_\(Int(Date().timeIntervalSince1970)).jpeg")//URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("vn_img_\(Int(Date().timeIntervalSince1970)).jpeg").absoluteString 404 | 405 | autoreleasepool { 406 | var imageData: Data? = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageSampleBuffer!) 407 | let image:UIImage = UIImage(data: imageData!)! 408 | var enhancedImage = CIImage(data: imageData!, options: convertToOptionalCIImageOptionDictionary([convertFromCIImageOption(CIImageOption.colorSpace): NSNull()])) 409 | imageData = nil 410 | if weakSelf?.cameraViewType == VNCameraViewType.blackAndWhite { 411 | enhancedImage = self.filteredImageUsingEnhanceFilter(on: enhancedImage!) 412 | } 413 | else { 414 | enhancedImage = self.filteredImageUsingContrastFilter(on: enhancedImage!) 415 | } 416 | if (weakSelf?.isEnableBorderDetection)! && self.rectangleDetectionConfidenceHighEnough(confidence: Float(self.imageDedectionConfidence)) { 417 | let rectangleFeature = self.biggestRectangle(inRectangles: (self.highAccuracyRectangleDetector()?.features(in: enhancedImage!))!) 418 | // let rectangleFeature: VNRectangleFeature? = VNRectangleFeature() 419 | // rectangleFeature?.bottomLeft = (rectFet?.bottomLeft)! 420 | // rectangleFeature?.bottomRight = (rectFet?.bottomRight)! 421 | // rectangleFeature?.topLeft = (rectFet?.topLeft)! 422 | // rectangleFeature?.topRight = (rectFet?.topRight)! 423 | 424 | if rectangleFeature != nil { 425 | enhancedImage = self.correctPerspective(for: enhancedImage!, withFeatures: rectangleFeature!) 426 | } 427 | } 428 | let transform = CIFilter(name: "CIAffineTransform") 429 | transform?.setValue(enhancedImage, forKey: kCIInputImageKey) 430 | let rotation = NSValue(cgAffineTransform: CGAffineTransform(rotationAngle: -90 * (.pi / 180))) 431 | transform?.setValue(rotation, forKey: "inputTransform") 432 | enhancedImage = transform?.outputImage 433 | if !(enhancedImage != nil) || (enhancedImage?.extent.isEmpty)! { 434 | return 435 | } 436 | var ctx: CIContext? = nil 437 | if ctx == nil { 438 | ctx = CIContext(options: convertToOptionalCIContextOptionDictionary([convertFromCIContextOption(CIContextOption.workingColorSpace): NSNull()])) 439 | } 440 | var bounds: CGSize = (enhancedImage?.extent.size)! 441 | // bounds = CGSize(width: CGFloat(floorf(bounds.width / 4) * 4), height: CGFloat(floorf(bounds.height / 4) * 4)) 442 | bounds = CGSize(width: (bounds.width/4)*4, height: (bounds.height/4)*4) 443 | let extent = CGRect(x: CGFloat((enhancedImage?.extent.origin.x)!), y: CGFloat((enhancedImage?.extent.origin.y)!), width: CGFloat(bounds.width), height: CGFloat(bounds.height)) 444 | let bytesPerPixel: Int = 8 445 | let rowBytes: uint = uint(Float(bytesPerPixel) * Float(bounds.width)) 446 | let totalBytes: uint = uint(Float(rowBytes) * Float(bounds.height)) 447 | let byteBuffer = malloc(Int(totalBytes)) 448 | let colorSpace: CGColorSpace? = CGColorSpaceCreateDeviceRGB() 449 | ctx?.render(enhancedImage!, toBitmap: byteBuffer!, rowBytes: Int(rowBytes), bounds: extent, format: CIFormat.RGBA8, colorSpace: colorSpace) 450 | let bitmapContext = CGContext(data: byteBuffer, width: Int(bounds.width), height: Int(bounds.height), bitsPerComponent: bytesPerPixel, bytesPerRow: Int(rowBytes), space: colorSpace!, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue)//kCGImageAlphaNoneSkipLast) 451 | let imgRef: CGImage? = bitmapContext?.makeImage() 452 | free(byteBuffer) 453 | if imgRef == nil { 454 | return 455 | } 456 | self.saveCGImageAsJPEGToFilePath(imgRef: imgRef!, filePath: filePath) 457 | DispatchQueue.main.async(execute: {() -> Void in 458 | completionHandler(filePath) 459 | self.captureQueue.resume() 460 | }) 461 | self.imageDedectionConfidence = 0.0 462 | 463 | } 464 | 465 | }) 466 | 467 | } 468 | 469 | func saveCGImageAsJPEGToFilePath(imgRef:CGImage, filePath:String){ 470 | 471 | let url: CFURL? = URL(fileURLWithPath: filePath) as CFURL 472 | if(url != nil) 473 | { 474 | guard let destination = CGImageDestinationCreateWithURL(url!, kUTTypePNG, 1, nil) else { print("error") 475 | return} 476 | CGImageDestinationAddImage(destination, imgRef, nil) 477 | CGImageDestinationFinalize(destination) 478 | } 479 | } 480 | 481 | func correctPerspective(for image: CIImage, withFeatures rectangleFeature: VNRectangleFeature) -> CIImage { 482 | var rectangleCoordinates = [String: Any]() 483 | rectangleCoordinates["inputTopLeft"] = CIVector(cgPoint: rectangleFeature.topLeft) 484 | rectangleCoordinates["inputTopRight"] = CIVector(cgPoint: rectangleFeature.topRight) 485 | rectangleCoordinates["inputBottomLeft"] = CIVector(cgPoint: rectangleFeature.bottomLeft) 486 | rectangleCoordinates["inputBottomRight"] = CIVector(cgPoint: rectangleFeature.bottomRight) 487 | return image.applyingFilter("CIPerspectiveCorrection", parameters: rectangleCoordinates) 488 | } 489 | } 490 | 491 | public extension DispatchQueue { 492 | 493 | private static var _onceTracker = [String]() 494 | 495 | /** 496 | Executes a block of code, associated with a unique token, only once. The code is thread safe and will 497 | only execute the code once even in the presence of multithreaded calls. 498 | 499 | - parameter token: A unique reverse DNS style name such as com.vectorform. or a GUID 500 | - parameter block: Block to execute once 501 | */ 502 | public class func once(token: String, block:()->Void) { 503 | objc_sync_enter(self); defer { objc_sync_exit(self) } 504 | 505 | if _onceTracker.contains(token) { 506 | return 507 | } 508 | 509 | _onceTracker.append(token) 510 | block() 511 | } 512 | 513 | 514 | } 515 | 516 | extension String { 517 | func stringByAppendingPathComponent(path: String) -> String { 518 | let nsSt = self as NSString 519 | return nsSt.appendingPathComponent(path) 520 | } 521 | } 522 | 523 | //extension CIRectangleFeature { 524 | // 525 | // convenience init(rectangleFeature feature: CIRectangleFeature) { 526 | // self.topLeft = feature.topLeft 527 | // self.topRight = feature.topRight 528 | // self.bottomRight = feature.bottomRight 529 | // self.bottomLeft = feature.bottomLeft 530 | // } 531 | //} 532 | 533 | // Helper function inserted by Swift 4.2 migrator. 534 | fileprivate func convertToOptionalCIContextOptionDictionary(_ input: [String: Any]?) -> [CIContextOption: Any]? { 535 | guard let input = input else { return nil } 536 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (CIContextOption(rawValue: key), value)}) 537 | } 538 | 539 | // Helper function inserted by Swift 4.2 migrator. 540 | fileprivate func convertFromCIContextOption(_ input: CIContextOption) -> String { 541 | return input.rawValue 542 | } 543 | 544 | // Helper function inserted by Swift 4.2 migrator. 545 | fileprivate func convertToOptionalCIImageOptionDictionary(_ input: [String: Any]?) -> [CIImageOption: Any]? { 546 | guard let input = input else { return nil } 547 | return Dictionary(uniqueKeysWithValues: input.map { key, value in (CIImageOption(rawValue: key), value)}) 548 | } 549 | 550 | // Helper function inserted by Swift 4.2 migrator. 551 | fileprivate func convertFromCIImageOption(_ input: CIImageOption) -> String { 552 | return input.rawValue 553 | } 554 | -------------------------------------------------------------------------------- /VNImageScanner/VNCameraScanner/VNCameraScanner.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 28 | 33 | 38 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /VNImageScanner/VNImageScanner.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | VNImageScanner.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /VNImageScanner/VNImageScanner.xcdatamodeld/VNImageScanner.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /VNImageScanner/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // VNImageScanner 4 | // 5 | // Created by Varun Naharia on 11/04/17. 6 | // Copyright © 2017 Varun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var titleLabel: UILabel! 14 | @IBOutlet weak var cameraViewController: VNCameraScanner! 15 | @IBOutlet weak var focusIndicator: UIImageView! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | // Do any additional setup after loading the view, typically from a nib. 20 | cameraViewController.setupCameraView() 21 | cameraViewController.isEnableBorderDetection = true 22 | updateTitleLabel() 23 | 24 | } 25 | 26 | override func didReceiveMemoryWarning() { 27 | super.didReceiveMemoryWarning() 28 | // Dispose of any resources that can be recreated. 29 | } 30 | 31 | override func viewDidAppear(_ animated: Bool) { 32 | cameraViewController.start() 33 | } 34 | 35 | @IBAction func focusGesture(_ sender: UITapGestureRecognizer) { 36 | if sender.state == UIGestureRecognizer.State.recognized { 37 | let location: CGPoint = sender.location(in: self.cameraViewController) 38 | focusIndicatorAnimate(to: location) 39 | cameraViewController.focus(at: location, completionHandler: {() -> Void in 40 | self.focusIndicatorAnimate(to: location) 41 | }) 42 | } 43 | } 44 | 45 | func focusIndicatorAnimate(to targetPoint: CGPoint) { 46 | focusIndicator.center = targetPoint 47 | focusIndicator.alpha = 0.0 48 | focusIndicator.isHidden = false 49 | UIView.animate(withDuration: 0.4, animations: {() -> Void in 50 | self.focusIndicator.alpha = 1.0 51 | }, completion: {(_ finished: Bool) -> Void in 52 | UIView.animate(withDuration: 0.4, animations: {() -> Void in 53 | self.focusIndicator.alpha = 0.0 54 | }) 55 | }) 56 | } 57 | 58 | func change(_ button: UIButton, targetTitle title: String, toStateEnabled enabled: Bool) { 59 | button.setTitle(title, for: .normal) 60 | button.setTitleColor((enabled) ? UIColor(red: CGFloat(1), green: CGFloat(0.81), blue: CGFloat(0), alpha: CGFloat(1)) : UIColor.white, for: .normal) 61 | } 62 | 63 | @IBAction func borderDetectToggle(_ sender: UIButton) { 64 | let enable: Bool = !cameraViewController.isEnableBorderDetection 65 | change(sender, targetTitle: (enable) ? "CROP On" : "CROP Off", toStateEnabled: enable) 66 | cameraViewController.isEnableBorderDetection = enable 67 | updateTitleLabel() 68 | } 69 | 70 | @IBAction func filterToggle(_ sender: Any) { 71 | cameraViewController.cameraViewType = (cameraViewController.cameraViewType == VNCameraViewType.blackAndWhite) ? VNCameraViewType.normal : VNCameraViewType.blackAndWhite 72 | updateTitleLabel() 73 | } 74 | 75 | @IBAction func torchToggle(_ sender: UIButton) { 76 | let enable: Bool = !cameraViewController.isEnableTorch 77 | change(sender, targetTitle: (enable) ? "FLASH On" : "FLASH Off", toStateEnabled: enable) 78 | cameraViewController.isEnableTorch = enable 79 | } 80 | 81 | func updateTitleLabel() { 82 | 83 | // let animation = CATransition.animation() 84 | let animation:CATransition = CATransition() 85 | animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) 86 | animation.type = CATransitionType.push 87 | animation.subtype = CATransitionSubtype.fromBottom 88 | animation.duration = 0.35 89 | titleLabel?.layer.add(animation, forKey: "kCATransitionFade") 90 | let filterMode: String = (cameraViewController.cameraViewType == VNCameraViewType.blackAndWhite) ? "TEXT FILTER" : "COLOR FILTER" 91 | titleLabel?.text = filterMode + (" | \((cameraViewController.isEnableBorderDetection) ? "AUTOCROP On" : "AUTOCROP Off")") 92 | } 93 | 94 | @IBAction func captureButton(_ sender: Any) { 95 | weak var weakSelf = self 96 | cameraViewController.captureImage(withCompletionHander: {(_ imageFilePath: String) -> Void in 97 | let captureImageView = UIImageView(image: UIImage(contentsOfFile: imageFilePath)) 98 | captureImageView.backgroundColor = UIColor(white: CGFloat(0.0), alpha: CGFloat(0.7)) 99 | captureImageView.frame = (weakSelf?.view.bounds.offsetBy(dx: CGFloat(0), dy: CGFloat((weakSelf?.view.bounds.size.height)!)))! 100 | captureImageView.alpha = 1.0 101 | captureImageView.contentMode = .scaleAspectFit 102 | captureImageView.isUserInteractionEnabled = true 103 | weakSelf?.view.addSubview(captureImageView) 104 | let dismissTap = UITapGestureRecognizer(target: weakSelf, action: #selector(self.dismissPreview)) 105 | captureImageView.addGestureRecognizer(dismissTap) 106 | UIView.animate(withDuration: 0.7, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.7, options: .allowUserInteraction, animations: {() -> Void in 107 | captureImageView.frame = (weakSelf?.view.bounds)! 108 | }, completion: { _ in }) 109 | }) 110 | } 111 | 112 | @objc func dismissPreview(_ dismissTap: UITapGestureRecognizer) { 113 | UIView.animate(withDuration: 0.7, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 1.0, options: .allowUserInteraction, animations: {() -> Void in 114 | dismissTap.view?.frame = self.view.bounds.offsetBy(dx: CGFloat(0), dy: CGFloat(self.view.bounds.size.height)) 115 | }, completion: {(_ finished: Bool) -> Void in 116 | dismissTap.view?.removeFromSuperview() 117 | }) 118 | } 119 | 120 | 121 | } 122 | 123 | -------------------------------------------------------------------------------- /VNImageScannerTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /VNImageScannerTests/VNImageScannerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VNImageScannerTests.swift 3 | // VNImageScannerTests 4 | // 5 | // Created by Varun Naharia on 11/04/17. 6 | // Copyright © 2017 Varun. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import VNImageScanner 11 | 12 | class VNImageScannerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /VNImageScannerUITests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /VNImageScannerUITests/VNImageScannerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VNImageScannerUITests.swift 3 | // VNImageScannerUITests 4 | // 5 | // Created by Varun Naharia on 11/04/17. 6 | // Copyright © 2017 Varun. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class VNImageScannerUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------