├── .gitignore ├── FaceLayout.jpg ├── LICENSE ├── Package.swift ├── README.md ├── VideoSample.MP4 ├── Warhol.podspec ├── Warhol ├── 0.1.1 │ └── Warhol.podspec ├── 0.1.2 │ └── Warhol.podspec ├── SampleApp │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Face.imageset │ │ │ ├── Contents.json │ │ │ └── faceImage.jpeg │ │ ├── leftEye.imageset │ │ │ ├── Contents.json │ │ │ └── Untitled.png │ │ ├── nose.imageset │ │ │ ├── Contents.json │ │ │ └── pngwave (3).png │ │ └── rightEye.imageset │ │ │ ├── Contents.json │ │ │ └── rightEye.png │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Info.plist │ ├── Main.storyboard │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── View │ │ └── CameraFrontView.swift │ └── ViewController │ │ ├── ImageViewController.swift │ │ └── ViewController.swift ├── Warhol.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── cesarvargas.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── cesarvargas.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── Warhol │ ├── CameraFaceDetectionViewController.swift │ ├── Extensions │ ├── AVCaptureVideoPreviewLayer+Landmarks.swift │ ├── CGRect+SizeFactor.swift │ ├── CoreGraphicsExtensions.swift │ ├── FaceLandmarkPerimeter+Rect.swift │ └── UIView+Constraints.swift │ ├── FaceDetector.swift │ ├── FaceImagesLayout │ ├── FaceImagesLayout.swift │ └── FaceLayoutCameraFrontView.swift │ ├── FaceViewModel.swift │ ├── Info.plist │ ├── Warhol.h │ └── Warhol.swift └── warhol.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /FaceLayout.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Warhol/a52a7248ac5fa1a45290959044d6cb0e0a1d667e/FaceLayout.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 César Vargas Casaseca 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | /** 4 | * Warhol 5 | * Copyright (c) César Vargas Casaseca 2020 6 | * Licensed under the MIT license (see LICENSE file) 7 | */ 8 | 9 | import PackageDescription 10 | 11 | let package = Package( 12 | name: "Warhol", 13 | platforms: [ 14 | .iOS(.v11)], 15 | products: [ 16 | .library( 17 | name: "Warhol", 18 | targets: ["Warhol"] 19 | ) 20 | ], 21 | targets: [ 22 | .target(name: "Warhol", 23 | path: "Warhol/Warhol") 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Warhol 3 |

4 | 5 |

6 | 7 | 8 | Swift Package Manager 9 | 10 | 11 | Carthage 12 | 13 | 14 | CocoaPods 15 | 16 | 17 | PRs Welcome 18 | 19 | 20 | Medium: @toupper 21 | 22 |

23 | 24 | Welcome to **Warhol** — A library written in Swift that makes easy the process of Face Detection and drawing on top for IOS. 25 | 26 | Warhol acts as a wrapper on top of the Apple Vision Framework, detecting the features of a face from camera or image and providing these elements position in your own coordinates, so you can easily draw on top. Forget about the complex Vision or AVFoundation frameworks, just handle the Warhol Face View Model class that encapsulates the features coordinates. 27 | 28 | ## Features 29 | 30 | - [x] Face Detection from Camera 31 | - [x] Face Detection from UIImageView 32 | - [x] Face features conversion to the client coordinates. 33 | - [x] Draw on top of the face. 34 | 35 | ## Requirements 36 | 37 | - iOS 11.0+ 38 | - Xcode 11.0+ 39 | 40 | ## Installation 41 | 42 | #### CocoaPods 43 | You can use [CocoaPods](http://cocoapods.org/) to install `Warhol` by adding it to your `Podfile`: 44 | 45 | ```ruby 46 | platform :ios, '11.0' 47 | use_frameworks! 48 | pod 'Warhol' 49 | ``` 50 | 51 | To get the full benefits import `Warhol` wherever you use it 52 | 53 | ``` swift 54 | import Warhol 55 | ``` 56 | ### Carthage 57 | 58 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. To integrate Alamofire into your Xcode project using Carthage, specify it in your `Cartfile`: 59 | 60 | ```ogdl 61 | github "toupper/Warhol" ~> 0.2.0 62 | ``` 63 | 64 | ### Swift Package Manager 65 | 66 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but Alamofire does support its use on supported platforms. 67 | 68 | Once you have your Swift package set up, adding Warhol as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. 69 | 70 | ```swift 71 | dependencies: [ 72 | .package(url: "https://github.com/toupper/Warhol.git", .upToNextMajor(from: "0.2.0")) 73 | ] 74 | ``` 75 | ## Manually 76 | 77 | You can also integrate Warhol into your project manually. 78 | 79 | #### Embedded Framework 80 | 81 | - Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository: 82 | 83 | ```bash 84 | $ git init 85 | ``` 86 | 87 | - Add Warhol as a git [submodule](https://git-scm.com/docs/git-submodule) by running the following command: 88 | 89 | ```bash 90 | $ git submodule add https://github.com/toupper/Warhol.git 91 | ``` 92 | 93 | - Open the new `Warhol` folder, and drag the `Warhol.xcodeproj` into the Project Navigator of your application's Xcode project. 94 | 95 | - And that's it! 96 | 97 | ## Usage example 98 | 99 | ### From Camera, Draw on Top 100 | 101 | Import Warhol in the file you are going to use it. Create an instance of ```CameraFaceDetectionViewController```, and asign the view where you are going to draw to the ```cameraFrontView``` property of the former. You can then present the view controller: 102 | 103 | ```swift 104 | import Warhol 105 | 106 | let cameraViewController = CameraFaceDetectionViewController() 107 | let faceView = FaceView() 108 | faceView.backgroundColor = .clear 109 | cameraViewController.cameraFrontView = faceView 110 | present(cameraViewController, animated: true, completion: nil) 111 | ``` 112 | 113 | In order to draw, we should create a subclass of UIView that complies with the Warhol protocol FaceView. We can then draw in their ```func draw(_ rect: CGRect)``` function. Anytime Warhol detects a Face change, it will call ```setNeedsDisplay()``` on the view so it can trigger the draw process: 114 | 115 | ```swift 116 | import Warhol 117 | 118 | final class FaceView: UIView, CameraFrontView { 119 | var viewModel: FaceViewModel? 120 | 121 | override func draw(_ rect: CGRect) { 122 | guard let context = UIGraphicsGetCurrentContext(), 123 | let viewModel = viewModel else { 124 | return 125 | } 126 | 127 | context.saveGState() 128 | 129 | defer { 130 | context.restoreGState() 131 | } 132 | 133 | context.addRect(viewModel.boundingBox) 134 | 135 | ... 136 | ``` 137 | 138 | ### From Camera, Add Images on Face Features 139 | 140 |

141 | FaceLayouts 142 |

143 | 144 | If you want to add images on top of each Face Features, you have to compose a ```FaceLayout``` object defining an ```ImageLayout``` object for each type of Face Landmark you want to draw. You can set the desired offset and Size Ratio for each feature. Once you have it, you should then pass it to the faceLayout property of the ```CameraFaceDetectionViewController```. Please notice that if you do that the ```cameraFrontView``` property gets overriden: 145 | 146 | ```swift 147 | let cameraViewController = CameraFaceDetectionViewController() 148 | 149 | let leftEye = ImageLayout(image: UIImage(named: "leftEye")!, sizeRatio: SizeRatio(width: 1, height: 4)) 150 | let rightEye = ImageLayout(image: UIImage(named: "rightEye")!, sizeRatio: SizeRatio(width: 1, height: 4)) 151 | let nose = ImageLayout(image: UIImage(named: "nose")!) 152 | 153 | let faceLayout = FaceLayout(landmarkLayouts: [.leftEye: leftEye, 154 | .rightEye: rightEye, 155 | .nose: nose]) 156 | cameraViewController.faceLayout = faceLayout 157 | 158 | present(cameraViewController, animated: true, completion: nil) 159 | ``` 160 | 161 | Apart from that, you can implement the ```CameraFaceDetectionDelegate``` protocol to react to any change in the Face Dectection. This can be convenient for the case when you do not want to draw on top, but just get the face features (landmarks) coordinates. These are encapsulated in the given parameter ```FaceViewModel```. 162 | ### From Image 163 | 164 | In order to detect a face features and draw on top, we should pass the sdk the UIImageView depicting the face, and a closure where we draw on top of the image: 165 | ```swift 166 | import Warhol 167 | 168 | imageView.image = UIImage(named: "Face") 169 | Warhol.drawLandmarks(from: imageView, 170 | draw: { (viewModel, context) in 171 | // draw with CGContext 172 | }, 173 | error: {_ in }) 174 | ``` 175 | 176 | If instead of modifying the passed image of the you want to generate a new UIImageView instance, use ```drawLandmarksInNewImage```: 177 | 178 | ```swift 179 | imageView.image = UIImage(named: "Face") 180 | Warhol.drawLandmarksInNewImage(from: imageView, 181 | draw: { (viewModel, context) in 182 | self.draw(viewModel: viewModel, in: context) 183 | }, 184 | completion: { newImage in 185 | self.newImageView.image = newImage 186 | }, 187 | error: {_ in }) 188 | ``` 189 | ## Contribute 190 | 191 | We would love you for the contribution to **Warhol**, check the ``LICENSE`` file for more info. 192 | 193 | ## Credits 194 | 195 | Created and maintained with love by [César Vargas Casaseca](https://github.com/toupper). You can follow me on Medium [@toupper](https://medium.com/@toupper) for project updates, releases and more stories. 196 | 197 | ## License 198 | 199 | Warhol is released under the MIT license. [See LICENSE](https://github.com/toupper/Warhol/blob/master/LICENSE) for details. 200 | -------------------------------------------------------------------------------- /VideoSample.MP4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Warhol/a52a7248ac5fa1a45290959044d6cb0e0a1d667e/VideoSample.MP4 -------------------------------------------------------------------------------- /Warhol.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint Warhol.podspec' to ensure this is a 3 | # valid spec and remove all comments before submitting the spec. 4 | # 5 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 6 | # 7 | 8 | Pod::Spec.new do |s| 9 | s.name = "Warhol" 10 | s.version = "0.2.0" 11 | s.summary = "Face detection made easy." 12 | s.description = <<-DESC 13 | Warhol acts as a wrapper on top of the Apple Vision Framework, detecting the features of a face from camera or image and providing these elements position in your own coordinates, so you can easily draw on top. 14 | DESC 15 | s.homepage = "https://github.com/toupper/Warhol" 16 | s.license = 'MIT' 17 | s.author = { "César Vargas Casaseca" => "c.vargas.casaseca@gmail.com" } 18 | s.source = { :git => "https://github.com/toupper/Warhol.git", :tag => s.version.to_s } 19 | 20 | s.ios.deployment_target = '11.0' 21 | 22 | s.requires_arc = true 23 | 24 | s.ios.source_files = 'Warhol/Warhol/**/*.{h,m,swift}' 25 | 26 | s.swift_version = "5.2" 27 | end 28 | -------------------------------------------------------------------------------- /Warhol/0.1.1/Warhol.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint Warhol.podspec' to ensure this is a 3 | # valid spec and remove all comments before submitting the spec. 4 | # 5 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 6 | # 7 | 8 | Pod::Spec.new do |s| 9 | s.name = "Warhol" 10 | s.version = "0.1.1" 11 | s.summary = "Face detection made easy." 12 | s.description = <<-DESC 13 | Warhol acts as a wrapper on top of the Apple Vision Framework, detecting the features of a face from camera or image and providing these elements position in your own coordinates, so you can easily draw on top. 14 | DESC 15 | s.homepage = "https://github.com/toupper/Warhol" 16 | s.license = 'MIT' 17 | s.author = { "César Vargas Casaseca" => "c.vargas.casaseca@gmail.com" } 18 | s.source = { :git => "https://github.com/toupper/Warhol.git", :tag => s.version.to_s } 19 | 20 | s.ios.deployment_target = '11.0' 21 | 22 | s.requires_arc = true 23 | 24 | s.ios.source_files = 'Warhol/Warhol/**/*.{h,m,swift}' 25 | 26 | s.swift_version = "5.2" 27 | end 28 | -------------------------------------------------------------------------------- /Warhol/0.1.2/Warhol.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint Warhol.podspec' to ensure this is a 3 | # valid spec and remove all comments before submitting the spec. 4 | # 5 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 6 | # 7 | 8 | Pod::Spec.new do |s| 9 | s.name = "Warhol" 10 | s.version = "0.1.2" 11 | s.summary = "Face detection made easy." 12 | s.description = <<-DESC 13 | Warhol acts as a wrapper on top of the Apple Vision Framework, detecting the features of a face from camera or image and providing these elements position in your own coordinates, so you can easily draw on top. 14 | DESC 15 | s.homepage = "https://github.com/toupper/Warhol" 16 | s.license = 'MIT' 17 | s.author = { "César Vargas Casaseca" => "c.vargas.casaseca@gmail.com" } 18 | s.source = { :git => "https://github.com/toupper/Warhol.git", :tag => s.version.to_s } 19 | 20 | s.ios.deployment_target = '11.0' 21 | 22 | s.requires_arc = true 23 | 24 | s.ios.source_files = 'Warhol/Warhol/**/*.{h,m,swift}' 25 | 26 | s.swift_version = "5.2" 27 | end 28 | -------------------------------------------------------------------------------- /Warhol/SampleApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SampleApp 4 | // 5 | // Created by Cesar Vargas on 23.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, 16 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Warhol/SampleApp/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 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/Face.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "faceImage.jpeg", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/Face.imageset/faceImage.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Warhol/a52a7248ac5fa1a45290959044d6cb0e0a1d667e/Warhol/SampleApp/Assets.xcassets/Face.imageset/faceImage.jpeg -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/leftEye.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Untitled.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/leftEye.imageset/Untitled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Warhol/a52a7248ac5fa1a45290959044d6cb0e0a1d667e/Warhol/SampleApp/Assets.xcassets/leftEye.imageset/Untitled.png -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/nose.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "pngwave (3).png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/nose.imageset/pngwave (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Warhol/a52a7248ac5fa1a45290959044d6cb0e0a1d667e/Warhol/SampleApp/Assets.xcassets/nose.imageset/pngwave (3).png -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/rightEye.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "rightEye.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Warhol/SampleApp/Assets.xcassets/rightEye.imageset/rightEye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Warhol/a52a7248ac5fa1a45290959044d6cb0e0a1d667e/Warhol/SampleApp/Assets.xcassets/rightEye.imageset/rightEye.png -------------------------------------------------------------------------------- /Warhol/SampleApp/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 | -------------------------------------------------------------------------------- /Warhol/SampleApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | Needs Camera to try Warhol 25 | NSMainStoryboardFile 26 | Main 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile~iphone 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Warhol/SampleApp/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 32 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 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 | 103 | -------------------------------------------------------------------------------- /Warhol/SampleApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Warhol/SampleApp/View/CameraFrontView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FaceView.swift 3 | // SampleApp 4 | // 5 | // Created by Cesar Vargas on 24.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Warhol 11 | import UIKit 12 | 13 | final class FaceView: UIView, CameraFrontView { 14 | var viewModel: FaceViewModel? 15 | 16 | override func draw(_ rect: CGRect) { 17 | guard let context = UIGraphicsGetCurrentContext(), 18 | let viewModel = viewModel else { 19 | return 20 | } 21 | 22 | context.saveGState() 23 | 24 | defer { 25 | context.restoreGState() 26 | } 27 | 28 | context.addRect(viewModel.boundingBox) 29 | 30 | UIColor.red.setStroke() 31 | context.strokePath() 32 | UIColor.white.setStroke() 33 | 34 | Array(viewModel.landmarks.values).forEach {self.draw(landmark: $0, closePath: true, in: context) } 35 | } 36 | 37 | private func draw(landmark: FaceLandmarkPerimeter, closePath: Bool, in context: CGContext) { 38 | guard !landmark.isEmpty else { 39 | return 40 | } 41 | 42 | context.addLines(between: landmark) 43 | 44 | if closePath { 45 | context.closePath() 46 | } 47 | 48 | context.strokePath() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Warhol/SampleApp/ViewController/ImageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewController.swift 3 | // SampleApp 4 | // 5 | // Created by Cesar Vargas on 26.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Warhol 12 | 13 | final class ImageViewController: UIViewController { 14 | @IBOutlet weak var imageView: UIImageView! 15 | @IBOutlet weak var faceView: FaceView! 16 | @IBOutlet weak var newImageView: UIImageView! 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | imageView.image = UIImage(named: "Face") 22 | Warhol.drawLandmarksInNewImage(from: imageView, 23 | draw: { (viewModel, context) in 24 | self.draw(viewModel: viewModel, in: context) 25 | }, completion: { newImage in 26 | self.newImageView.image = newImage 27 | }, 28 | error: {_ in }) 29 | } 30 | 31 | private func draw(viewModel: FaceViewModel, in context: CGContext) { 32 | context.saveGState() 33 | 34 | defer { 35 | context.restoreGState() 36 | } 37 | 38 | context.addRect(viewModel.boundingBox) 39 | 40 | UIColor.red.setStroke() 41 | context.strokePath() 42 | UIColor.blue.setStroke() 43 | 44 | Array(viewModel.landmarks.values).forEach { self.draw(landmark: $0, closePath: true, in: context) } 45 | } 46 | 47 | private func draw(landmark: FaceLandmarkPerimeter, closePath: Bool, in context: CGContext) { 48 | guard !landmark.isEmpty else { 49 | return 50 | } 51 | 52 | context.addLines(between: landmark) 53 | 54 | if closePath { 55 | context.closePath() 56 | } 57 | 58 | context.strokePath() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Warhol/SampleApp/ViewController/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SampleApp 4 | // 5 | // Created by Vargas Casaseca, Cesar on 18.06.18. 6 | // Copyright © 2018 Vargas Casaseca, Cesar. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Warhol 11 | 12 | class ViewController: UIViewController { 13 | @IBAction func openCameraWithClientDrawingButtonWasPressed(_ sender: Any) { 14 | let cameraViewController = CameraFaceDetectionViewController() 15 | 16 | let faceView = FaceView() 17 | faceView.backgroundColor = .clear 18 | cameraViewController.cameraFrontView = faceView 19 | 20 | present(cameraViewController, animated: true, completion: nil) 21 | } 22 | 23 | @IBAction func openCameraWithImageFeatures(_ sender: Any) { 24 | let cameraViewController = CameraFaceDetectionViewController() 25 | 26 | let leftEye = ImageLayout(image: UIImage(named: "leftEye")!, 27 | offset: CGPoint(x: 0, y: 50), 28 | sizeRatio: SizeRatio(width: 1, height: 4)) 29 | let rightEye = ImageLayout(image: UIImage(named: "rightEye")!, 30 | offset: CGPoint(x: 0, y: 50), 31 | sizeRatio: SizeRatio(width: 1, height: 4)) 32 | let nose = ImageLayout(image: UIImage(named: "nose")!) 33 | 34 | let faceLayout = FaceLayout(landmarkLayouts: [.leftEye: leftEye, 35 | .rightEye: rightEye, 36 | .nose: nose]) 37 | cameraViewController.faceLayout = faceLayout 38 | 39 | present(cameraViewController, animated: true, completion: nil) 40 | 41 | } 42 | 43 | @IBAction func openImageButtonWasPressed(_ sender: Any) { 44 | let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) 45 | let imageViewController = mainStoryboard.instantiateViewController(withIdentifier: "ImageViewController") 46 | present(imageViewController, animated: true, completion: nil) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Warhol/Warhol.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 372C82FE24390777005115DB /* CoreGraphicsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372C82FD24390777005115DB /* CoreGraphicsExtensions.swift */; }; 11 | 373EE90E245C2DDB004E5E6F /* FaceImagesLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373EE90D245C2DDB004E5E6F /* FaceImagesLayout.swift */; }; 12 | 373EE910245C393D004E5E6F /* FaceLayoutCameraFrontView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373EE90F245C393D004E5E6F /* FaceLayoutCameraFrontView.swift */; }; 13 | 373EE912245D8287004E5E6F /* CGRect+SizeFactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373EE911245D8287004E5E6F /* CGRect+SizeFactor.swift */; }; 14 | 373EE914245EDD9F004E5E6F /* FaceLandmarkPerimeter+Rect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373EE913245EDD9F004E5E6F /* FaceLandmarkPerimeter+Rect.swift */; }; 15 | 377F6FFF242CB14C0043BA3C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377F6FFE242CB14C0043BA3C /* ImageViewController.swift */; }; 16 | 377F70132430FF770043BA3C /* FaceDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377F70122430FF770043BA3C /* FaceDetector.swift */; }; 17 | 377F7026243395060043BA3C /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377F7025243395060043BA3C /* UIView+Constraints.swift */; }; 18 | 379D3E89242A23C90012C71A /* Warhol.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37FE0DD124280B2200B95287 /* Warhol.framework */; }; 19 | 379D3E8A242A23C90012C71A /* Warhol.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37FE0DD124280B2200B95287 /* Warhol.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 20 | 379D3E8F242A2EBB0012C71A /* CameraFrontView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379D3E8E242A2EBB0012C71A /* CameraFrontView.swift */; }; 21 | 37FE0DD624280B2200B95287 /* Warhol.h in Headers */ = {isa = PBXBuildFile; fileRef = 37FE0DD424280B2200B95287 /* Warhol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 22 | 37FE0DF524280BC100B95287 /* Warhol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FE0DF424280BC100B95287 /* Warhol.swift */; }; 23 | 37FE0DF724280BF800B95287 /* CameraFaceDetectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FE0DF624280BF800B95287 /* CameraFaceDetectionViewController.swift */; }; 24 | 37FE0DFA2428BD5900B95287 /* FaceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FE0DF92428BD5900B95287 /* FaceViewModel.swift */; }; 25 | 37FE0DFF2428C50500B95287 /* AVCaptureVideoPreviewLayer+Landmarks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FE0DFE2428C50500B95287 /* AVCaptureVideoPreviewLayer+Landmarks.swift */; }; 26 | 37FE0E0D2428C8CF00B95287 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FE0E0C2428C8CF00B95287 /* AppDelegate.swift */; }; 27 | 37FE0E132428C8D100B95287 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37FE0E122428C8D100B95287 /* Assets.xcassets */; }; 28 | 37FE0E162428C8D100B95287 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37FE0E152428C8D100B95287 /* Preview Assets.xcassets */; }; 29 | 37FE0E192428C8D100B95287 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 37FE0E172428C8D100B95287 /* LaunchScreen.storyboard */; }; 30 | 37FE0E1F2428C9BD00B95287 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 37FE0E1E2428C9BD00B95287 /* Main.storyboard */; }; 31 | 37FE0E212428C9C900B95287 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FE0E202428C9C900B95287 /* ViewController.swift */; }; 32 | /* End PBXBuildFile section */ 33 | 34 | /* Begin PBXContainerItemProxy section */ 35 | 379D3E8B242A23C90012C71A /* PBXContainerItemProxy */ = { 36 | isa = PBXContainerItemProxy; 37 | containerPortal = 37FE0DC824280B2200B95287 /* Project object */; 38 | proxyType = 1; 39 | remoteGlobalIDString = 37FE0DD024280B2200B95287; 40 | remoteInfo = Warhol; 41 | }; 42 | /* End PBXContainerItemProxy section */ 43 | 44 | /* Begin PBXCopyFilesBuildPhase section */ 45 | 379D3E8D242A23C90012C71A /* Embed Frameworks */ = { 46 | isa = PBXCopyFilesBuildPhase; 47 | buildActionMask = 2147483647; 48 | dstPath = ""; 49 | dstSubfolderSpec = 10; 50 | files = ( 51 | 379D3E8A242A23C90012C71A /* Warhol.framework in Embed Frameworks */, 52 | ); 53 | name = "Embed Frameworks"; 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXCopyFilesBuildPhase section */ 57 | 58 | /* Begin PBXFileReference section */ 59 | 372C82FD24390777005115DB /* CoreGraphicsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreGraphicsExtensions.swift; sourceTree = ""; }; 60 | 373EE90D245C2DDB004E5E6F /* FaceImagesLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceImagesLayout.swift; sourceTree = ""; }; 61 | 373EE90F245C393D004E5E6F /* FaceLayoutCameraFrontView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceLayoutCameraFrontView.swift; sourceTree = ""; }; 62 | 373EE911245D8287004E5E6F /* CGRect+SizeFactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect+SizeFactor.swift"; sourceTree = ""; }; 63 | 373EE913245EDD9F004E5E6F /* FaceLandmarkPerimeter+Rect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FaceLandmarkPerimeter+Rect.swift"; sourceTree = ""; }; 64 | 377F6FFE242CB14C0043BA3C /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; 65 | 377F70122430FF770043BA3C /* FaceDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceDetector.swift; sourceTree = ""; }; 66 | 377F7025243395060043BA3C /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; 67 | 379D3E8E242A2EBB0012C71A /* CameraFrontView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFrontView.swift; sourceTree = ""; }; 68 | 37FE0DD124280B2200B95287 /* Warhol.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Warhol.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 69 | 37FE0DD424280B2200B95287 /* Warhol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Warhol.h; sourceTree = ""; }; 70 | 37FE0DD524280B2200B95287 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 71 | 37FE0DF424280BC100B95287 /* Warhol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Warhol.swift; sourceTree = ""; }; 72 | 37FE0DF624280BF800B95287 /* CameraFaceDetectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraFaceDetectionViewController.swift; sourceTree = ""; }; 73 | 37FE0DF92428BD5900B95287 /* FaceViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaceViewModel.swift; sourceTree = ""; }; 74 | 37FE0DFE2428C50500B95287 /* AVCaptureVideoPreviewLayer+Landmarks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureVideoPreviewLayer+Landmarks.swift"; sourceTree = ""; }; 75 | 37FE0E0A2428C8CF00B95287 /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | 37FE0E0C2428C8CF00B95287 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 77 | 37FE0E122428C8D100B95287 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 78 | 37FE0E152428C8D100B95287 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 79 | 37FE0E182428C8D100B95287 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 80 | 37FE0E1A2428C8D100B95287 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 81 | 37FE0E1E2428C9BD00B95287 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 82 | 37FE0E202428C9C900B95287 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 83 | /* End PBXFileReference section */ 84 | 85 | /* Begin PBXFrameworksBuildPhase section */ 86 | 37FE0DCE24280B2200B95287 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | 37FE0E072428C8CF00B95287 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | 379D3E89242A23C90012C71A /* Warhol.framework in Frameworks */, 98 | ); 99 | runOnlyForDeploymentPostprocessing = 0; 100 | }; 101 | /* End PBXFrameworksBuildPhase section */ 102 | 103 | /* Begin PBXGroup section */ 104 | 373EE915245EDF7B004E5E6F /* FaceImagesLayout */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 373EE90F245C393D004E5E6F /* FaceLayoutCameraFrontView.swift */, 108 | 373EE90D245C2DDB004E5E6F /* FaceImagesLayout.swift */, 109 | ); 110 | path = FaceImagesLayout; 111 | sourceTree = ""; 112 | }; 113 | 377F702724339D680043BA3C /* ViewController */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 37FE0E202428C9C900B95287 /* ViewController.swift */, 117 | 377F6FFE242CB14C0043BA3C /* ImageViewController.swift */, 118 | ); 119 | path = ViewController; 120 | sourceTree = ""; 121 | }; 122 | 377F702824339D750043BA3C /* View */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 379D3E8E242A2EBB0012C71A /* CameraFrontView.swift */, 126 | ); 127 | path = View; 128 | sourceTree = ""; 129 | }; 130 | 37FE0DC724280B2200B95287 = { 131 | isa = PBXGroup; 132 | children = ( 133 | 37FE0DD324280B2200B95287 /* Warhol */, 134 | 37FE0E0B2428C8CF00B95287 /* SampleApp */, 135 | 37FE0DD224280B2200B95287 /* Products */, 136 | 37FE0E002428C86A00B95287 /* Frameworks */, 137 | ); 138 | sourceTree = ""; 139 | }; 140 | 37FE0DD224280B2200B95287 /* Products */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 37FE0DD124280B2200B95287 /* Warhol.framework */, 144 | 37FE0E0A2428C8CF00B95287 /* SampleApp.app */, 145 | ); 146 | name = Products; 147 | sourceTree = ""; 148 | }; 149 | 37FE0DD324280B2200B95287 /* Warhol */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 373EE915245EDF7B004E5E6F /* FaceImagesLayout */, 153 | 37FE0DFB2428C0E500B95287 /* Extensions */, 154 | 37FE0DD424280B2200B95287 /* Warhol.h */, 155 | 37FE0DD524280B2200B95287 /* Info.plist */, 156 | 37FE0DF424280BC100B95287 /* Warhol.swift */, 157 | 37FE0DF624280BF800B95287 /* CameraFaceDetectionViewController.swift */, 158 | 37FE0DF92428BD5900B95287 /* FaceViewModel.swift */, 159 | 377F70122430FF770043BA3C /* FaceDetector.swift */, 160 | ); 161 | path = Warhol; 162 | sourceTree = ""; 163 | }; 164 | 37FE0DFB2428C0E500B95287 /* Extensions */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 372C82FD24390777005115DB /* CoreGraphicsExtensions.swift */, 168 | 37FE0DFE2428C50500B95287 /* AVCaptureVideoPreviewLayer+Landmarks.swift */, 169 | 377F7025243395060043BA3C /* UIView+Constraints.swift */, 170 | 373EE911245D8287004E5E6F /* CGRect+SizeFactor.swift */, 171 | 373EE913245EDD9F004E5E6F /* FaceLandmarkPerimeter+Rect.swift */, 172 | ); 173 | path = Extensions; 174 | sourceTree = ""; 175 | }; 176 | 37FE0E002428C86A00B95287 /* Frameworks */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | ); 180 | name = Frameworks; 181 | sourceTree = ""; 182 | }; 183 | 37FE0E0B2428C8CF00B95287 /* SampleApp */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | 377F702824339D750043BA3C /* View */, 187 | 377F702724339D680043BA3C /* ViewController */, 188 | 37FE0E1E2428C9BD00B95287 /* Main.storyboard */, 189 | 37FE0E0C2428C8CF00B95287 /* AppDelegate.swift */, 190 | 37FE0E122428C8D100B95287 /* Assets.xcassets */, 191 | 37FE0E172428C8D100B95287 /* LaunchScreen.storyboard */, 192 | 37FE0E1A2428C8D100B95287 /* Info.plist */, 193 | 37FE0E142428C8D100B95287 /* Preview Content */, 194 | ); 195 | path = SampleApp; 196 | sourceTree = ""; 197 | }; 198 | 37FE0E142428C8D100B95287 /* Preview Content */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 37FE0E152428C8D100B95287 /* Preview Assets.xcassets */, 202 | ); 203 | path = "Preview Content"; 204 | sourceTree = ""; 205 | }; 206 | /* End PBXGroup section */ 207 | 208 | /* Begin PBXHeadersBuildPhase section */ 209 | 37FE0DCC24280B2200B95287 /* Headers */ = { 210 | isa = PBXHeadersBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 37FE0DD624280B2200B95287 /* Warhol.h in Headers */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXHeadersBuildPhase section */ 218 | 219 | /* Begin PBXNativeTarget section */ 220 | 37FE0DD024280B2200B95287 /* Warhol */ = { 221 | isa = PBXNativeTarget; 222 | buildConfigurationList = 37FE0DD924280B2300B95287 /* Build configuration list for PBXNativeTarget "Warhol" */; 223 | buildPhases = ( 224 | 37FE0DCC24280B2200B95287 /* Headers */, 225 | 37FE0DCD24280B2200B95287 /* Sources */, 226 | 377F701624320F9D0043BA3C /* Swift Lint Check */, 227 | 37FE0DCE24280B2200B95287 /* Frameworks */, 228 | 37FE0DCF24280B2200B95287 /* Resources */, 229 | ); 230 | buildRules = ( 231 | ); 232 | dependencies = ( 233 | ); 234 | name = Warhol; 235 | productName = Warhol; 236 | productReference = 37FE0DD124280B2200B95287 /* Warhol.framework */; 237 | productType = "com.apple.product-type.framework"; 238 | }; 239 | 37FE0E092428C8CF00B95287 /* SampleApp */ = { 240 | isa = PBXNativeTarget; 241 | buildConfigurationList = 37FE0E1B2428C8D100B95287 /* Build configuration list for PBXNativeTarget "SampleApp" */; 242 | buildPhases = ( 243 | 37FE0E062428C8CF00B95287 /* Sources */, 244 | 37FE0E072428C8CF00B95287 /* Frameworks */, 245 | 37FE0E082428C8CF00B95287 /* Resources */, 246 | 379D3E8D242A23C90012C71A /* Embed Frameworks */, 247 | ); 248 | buildRules = ( 249 | ); 250 | dependencies = ( 251 | 379D3E8C242A23C90012C71A /* PBXTargetDependency */, 252 | ); 253 | name = SampleApp; 254 | productName = SampleApp; 255 | productReference = 37FE0E0A2428C8CF00B95287 /* SampleApp.app */; 256 | productType = "com.apple.product-type.application"; 257 | }; 258 | /* End PBXNativeTarget section */ 259 | 260 | /* Begin PBXProject section */ 261 | 37FE0DC824280B2200B95287 /* Project object */ = { 262 | isa = PBXProject; 263 | attributes = { 264 | LastSwiftUpdateCheck = 1120; 265 | LastUpgradeCheck = 1200; 266 | ORGANIZATIONNAME = "Cesar Vargas"; 267 | TargetAttributes = { 268 | 37FE0DD024280B2200B95287 = { 269 | CreatedOnToolsVersion = 11.2.1; 270 | LastSwiftMigration = 1120; 271 | }; 272 | 37FE0E092428C8CF00B95287 = { 273 | CreatedOnToolsVersion = 11.2.1; 274 | }; 275 | }; 276 | }; 277 | buildConfigurationList = 37FE0DCB24280B2200B95287 /* Build configuration list for PBXProject "Warhol" */; 278 | compatibilityVersion = "Xcode 9.3"; 279 | developmentRegion = en; 280 | hasScannedForEncodings = 0; 281 | knownRegions = ( 282 | en, 283 | Base, 284 | ); 285 | mainGroup = 37FE0DC724280B2200B95287; 286 | productRefGroup = 37FE0DD224280B2200B95287 /* Products */; 287 | projectDirPath = ""; 288 | projectRoot = ""; 289 | targets = ( 290 | 37FE0DD024280B2200B95287 /* Warhol */, 291 | 37FE0E092428C8CF00B95287 /* SampleApp */, 292 | ); 293 | }; 294 | /* End PBXProject section */ 295 | 296 | /* Begin PBXResourcesBuildPhase section */ 297 | 37FE0DCF24280B2200B95287 /* Resources */ = { 298 | isa = PBXResourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | 37FE0E082428C8CF00B95287 /* Resources */ = { 305 | isa = PBXResourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 37FE0E192428C8D100B95287 /* LaunchScreen.storyboard in Resources */, 309 | 37FE0E162428C8D100B95287 /* Preview Assets.xcassets in Resources */, 310 | 37FE0E132428C8D100B95287 /* Assets.xcassets in Resources */, 311 | 37FE0E1F2428C9BD00B95287 /* Main.storyboard in Resources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | /* End PBXResourcesBuildPhase section */ 316 | 317 | /* Begin PBXShellScriptBuildPhase section */ 318 | 377F701624320F9D0043BA3C /* Swift Lint Check */ = { 319 | isa = PBXShellScriptBuildPhase; 320 | buildActionMask = 2147483647; 321 | files = ( 322 | ); 323 | inputFileListPaths = ( 324 | ); 325 | inputPaths = ( 326 | ); 327 | name = "Swift Lint Check"; 328 | outputFileListPaths = ( 329 | ); 330 | outputPaths = ( 331 | ); 332 | runOnlyForDeploymentPostprocessing = 0; 333 | shellPath = /bin/sh; 334 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 335 | }; 336 | /* End PBXShellScriptBuildPhase section */ 337 | 338 | /* Begin PBXSourcesBuildPhase section */ 339 | 37FE0DCD24280B2200B95287 /* Sources */ = { 340 | isa = PBXSourcesBuildPhase; 341 | buildActionMask = 2147483647; 342 | files = ( 343 | 37FE0DF724280BF800B95287 /* CameraFaceDetectionViewController.swift in Sources */, 344 | 37FE0DFF2428C50500B95287 /* AVCaptureVideoPreviewLayer+Landmarks.swift in Sources */, 345 | 377F7026243395060043BA3C /* UIView+Constraints.swift in Sources */, 346 | 373EE912245D8287004E5E6F /* CGRect+SizeFactor.swift in Sources */, 347 | 37FE0DFA2428BD5900B95287 /* FaceViewModel.swift in Sources */, 348 | 373EE90E245C2DDB004E5E6F /* FaceImagesLayout.swift in Sources */, 349 | 373EE914245EDD9F004E5E6F /* FaceLandmarkPerimeter+Rect.swift in Sources */, 350 | 372C82FE24390777005115DB /* CoreGraphicsExtensions.swift in Sources */, 351 | 377F70132430FF770043BA3C /* FaceDetector.swift in Sources */, 352 | 373EE910245C393D004E5E6F /* FaceLayoutCameraFrontView.swift in Sources */, 353 | 37FE0DF524280BC100B95287 /* Warhol.swift in Sources */, 354 | ); 355 | runOnlyForDeploymentPostprocessing = 0; 356 | }; 357 | 37FE0E062428C8CF00B95287 /* Sources */ = { 358 | isa = PBXSourcesBuildPhase; 359 | buildActionMask = 2147483647; 360 | files = ( 361 | 37FE0E212428C9C900B95287 /* ViewController.swift in Sources */, 362 | 37FE0E0D2428C8CF00B95287 /* AppDelegate.swift in Sources */, 363 | 377F6FFF242CB14C0043BA3C /* ImageViewController.swift in Sources */, 364 | 379D3E8F242A2EBB0012C71A /* CameraFrontView.swift in Sources */, 365 | ); 366 | runOnlyForDeploymentPostprocessing = 0; 367 | }; 368 | /* End PBXSourcesBuildPhase section */ 369 | 370 | /* Begin PBXTargetDependency section */ 371 | 379D3E8C242A23C90012C71A /* PBXTargetDependency */ = { 372 | isa = PBXTargetDependency; 373 | target = 37FE0DD024280B2200B95287 /* Warhol */; 374 | targetProxy = 379D3E8B242A23C90012C71A /* PBXContainerItemProxy */; 375 | }; 376 | /* End PBXTargetDependency section */ 377 | 378 | /* Begin PBXVariantGroup section */ 379 | 37FE0E172428C8D100B95287 /* LaunchScreen.storyboard */ = { 380 | isa = PBXVariantGroup; 381 | children = ( 382 | 37FE0E182428C8D100B95287 /* Base */, 383 | ); 384 | name = LaunchScreen.storyboard; 385 | sourceTree = ""; 386 | }; 387 | /* End PBXVariantGroup section */ 388 | 389 | /* Begin XCBuildConfiguration section */ 390 | 37FE0DD724280B2300B95287 /* Debug */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | ALWAYS_SEARCH_USER_PATHS = NO; 394 | CLANG_ANALYZER_NONNULL = YES; 395 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 396 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 397 | CLANG_CXX_LIBRARY = "libc++"; 398 | CLANG_ENABLE_MODULES = YES; 399 | CLANG_ENABLE_OBJC_ARC = YES; 400 | CLANG_ENABLE_OBJC_WEAK = YES; 401 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 402 | CLANG_WARN_BOOL_CONVERSION = YES; 403 | CLANG_WARN_COMMA = YES; 404 | CLANG_WARN_CONSTANT_CONVERSION = YES; 405 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 406 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 407 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INFINITE_RECURSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 414 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 416 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 421 | CLANG_WARN_UNREACHABLE_CODE = YES; 422 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 423 | COPY_PHASE_STRIP = NO; 424 | CURRENT_PROJECT_VERSION = 1; 425 | DEBUG_INFORMATION_FORMAT = dwarf; 426 | ENABLE_STRICT_OBJC_MSGSEND = YES; 427 | ENABLE_TESTABILITY = YES; 428 | GCC_C_LANGUAGE_STANDARD = gnu11; 429 | GCC_DYNAMIC_NO_PIC = NO; 430 | GCC_NO_COMMON_BLOCKS = YES; 431 | GCC_OPTIMIZATION_LEVEL = 0; 432 | GCC_PREPROCESSOR_DEFINITIONS = ( 433 | "DEBUG=1", 434 | "$(inherited)", 435 | ); 436 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 437 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 438 | GCC_WARN_UNDECLARED_SELECTOR = YES; 439 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 440 | GCC_WARN_UNUSED_FUNCTION = YES; 441 | GCC_WARN_UNUSED_VARIABLE = YES; 442 | INSTALL_PATH = ""; 443 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 444 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 445 | MTL_FAST_MATH = YES; 446 | ONLY_ACTIVE_ARCH = YES; 447 | SDKROOT = iphoneos; 448 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 449 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 450 | VERSIONING_SYSTEM = "apple-generic"; 451 | VERSION_INFO_PREFIX = ""; 452 | }; 453 | name = Debug; 454 | }; 455 | 37FE0DD824280B2300B95287 /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | buildSettings = { 458 | ALWAYS_SEARCH_USER_PATHS = NO; 459 | CLANG_ANALYZER_NONNULL = YES; 460 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 461 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 462 | CLANG_CXX_LIBRARY = "libc++"; 463 | CLANG_ENABLE_MODULES = YES; 464 | CLANG_ENABLE_OBJC_ARC = YES; 465 | CLANG_ENABLE_OBJC_WEAK = YES; 466 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 467 | CLANG_WARN_BOOL_CONVERSION = YES; 468 | CLANG_WARN_COMMA = YES; 469 | CLANG_WARN_CONSTANT_CONVERSION = YES; 470 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 471 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 472 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 473 | CLANG_WARN_EMPTY_BODY = YES; 474 | CLANG_WARN_ENUM_CONVERSION = YES; 475 | CLANG_WARN_INFINITE_RECURSION = YES; 476 | CLANG_WARN_INT_CONVERSION = YES; 477 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 478 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 479 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 480 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 481 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 482 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 483 | CLANG_WARN_STRICT_PROTOTYPES = YES; 484 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 485 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 486 | CLANG_WARN_UNREACHABLE_CODE = YES; 487 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 488 | COPY_PHASE_STRIP = NO; 489 | CURRENT_PROJECT_VERSION = 1; 490 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 491 | ENABLE_NS_ASSERTIONS = NO; 492 | ENABLE_STRICT_OBJC_MSGSEND = YES; 493 | GCC_C_LANGUAGE_STANDARD = gnu11; 494 | GCC_NO_COMMON_BLOCKS = YES; 495 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 496 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 497 | GCC_WARN_UNDECLARED_SELECTOR = YES; 498 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 499 | GCC_WARN_UNUSED_FUNCTION = YES; 500 | GCC_WARN_UNUSED_VARIABLE = YES; 501 | INSTALL_PATH = ""; 502 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 503 | MTL_ENABLE_DEBUG_INFO = NO; 504 | MTL_FAST_MATH = YES; 505 | SDKROOT = iphoneos; 506 | SWIFT_COMPILATION_MODE = wholemodule; 507 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 508 | VALIDATE_PRODUCT = YES; 509 | VERSIONING_SYSTEM = "apple-generic"; 510 | VERSION_INFO_PREFIX = ""; 511 | }; 512 | name = Release; 513 | }; 514 | 37FE0DDA24280B2300B95287 /* Debug */ = { 515 | isa = XCBuildConfiguration; 516 | buildSettings = { 517 | CLANG_ENABLE_MODULES = YES; 518 | CODE_SIGN_IDENTITY = ""; 519 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 520 | CODE_SIGN_STYLE = Automatic; 521 | DEFINES_MODULE = YES; 522 | DEVELOPMENT_TEAM = 67VC2B47W7; 523 | DYLIB_COMPATIBILITY_VERSION = 1; 524 | DYLIB_CURRENT_VERSION = 1; 525 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 526 | INFOPLIST_FILE = Warhol/Info.plist; 527 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 528 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 529 | LD_RUNPATH_SEARCH_PATHS = ( 530 | "$(inherited)", 531 | "@executable_path/Frameworks", 532 | "@loader_path/Frameworks", 533 | ); 534 | PRODUCT_BUNDLE_IDENTIFIER = com.casaseca.Warhol; 535 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 536 | PROVISIONING_PROFILE_SPECIFIER = ""; 537 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 538 | SKIP_INSTALL = YES; 539 | SUPPORTS_MACCATALYST = NO; 540 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 541 | SWIFT_VERSION = 5.0; 542 | TARGETED_DEVICE_FAMILY = "1,2"; 543 | }; 544 | name = Debug; 545 | }; 546 | 37FE0DDB24280B2300B95287 /* Release */ = { 547 | isa = XCBuildConfiguration; 548 | buildSettings = { 549 | CLANG_ENABLE_MODULES = YES; 550 | CODE_SIGN_IDENTITY = ""; 551 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 552 | CODE_SIGN_STYLE = Automatic; 553 | DEFINES_MODULE = YES; 554 | DEVELOPMENT_TEAM = 67VC2B47W7; 555 | DYLIB_COMPATIBILITY_VERSION = 1; 556 | DYLIB_CURRENT_VERSION = 1; 557 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 558 | INFOPLIST_FILE = Warhol/Info.plist; 559 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 560 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 561 | LD_RUNPATH_SEARCH_PATHS = ( 562 | "$(inherited)", 563 | "@executable_path/Frameworks", 564 | "@loader_path/Frameworks", 565 | ); 566 | PRODUCT_BUNDLE_IDENTIFIER = com.casaseca.Warhol; 567 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 568 | PROVISIONING_PROFILE_SPECIFIER = ""; 569 | "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; 570 | SKIP_INSTALL = YES; 571 | SUPPORTS_MACCATALYST = NO; 572 | SWIFT_VERSION = 5.0; 573 | TARGETED_DEVICE_FAMILY = "1,2"; 574 | }; 575 | name = Release; 576 | }; 577 | 37FE0E1C2428C8D100B95287 /* Debug */ = { 578 | isa = XCBuildConfiguration; 579 | buildSettings = { 580 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 581 | CODE_SIGN_STYLE = Automatic; 582 | DEVELOPMENT_ASSET_PATHS = ""; 583 | DEVELOPMENT_TEAM = EKP9ZV782L; 584 | ENABLE_PREVIEWS = YES; 585 | INFOPLIST_FILE = SampleApp/Info.plist; 586 | LD_RUNPATH_SEARCH_PATHS = ( 587 | "$(inherited)", 588 | "@executable_path/Frameworks", 589 | ); 590 | PRODUCT_BUNDLE_IDENTIFIER = de.axelspringer.weltmobil.warhol; 591 | PRODUCT_NAME = "$(TARGET_NAME)"; 592 | SWIFT_VERSION = 5.0; 593 | TARGETED_DEVICE_FAMILY = "1,2"; 594 | }; 595 | name = Debug; 596 | }; 597 | 37FE0E1D2428C8D100B95287 /* Release */ = { 598 | isa = XCBuildConfiguration; 599 | buildSettings = { 600 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 601 | CODE_SIGN_STYLE = Automatic; 602 | DEVELOPMENT_ASSET_PATHS = ""; 603 | DEVELOPMENT_TEAM = EKP9ZV782L; 604 | ENABLE_PREVIEWS = YES; 605 | INFOPLIST_FILE = SampleApp/Info.plist; 606 | LD_RUNPATH_SEARCH_PATHS = ( 607 | "$(inherited)", 608 | "@executable_path/Frameworks", 609 | ); 610 | PRODUCT_BUNDLE_IDENTIFIER = de.axelspringer.weltmobil.warhol; 611 | PRODUCT_NAME = "$(TARGET_NAME)"; 612 | SWIFT_VERSION = 5.0; 613 | TARGETED_DEVICE_FAMILY = "1,2"; 614 | }; 615 | name = Release; 616 | }; 617 | /* End XCBuildConfiguration section */ 618 | 619 | /* Begin XCConfigurationList section */ 620 | 37FE0DCB24280B2200B95287 /* Build configuration list for PBXProject "Warhol" */ = { 621 | isa = XCConfigurationList; 622 | buildConfigurations = ( 623 | 37FE0DD724280B2300B95287 /* Debug */, 624 | 37FE0DD824280B2300B95287 /* Release */, 625 | ); 626 | defaultConfigurationIsVisible = 0; 627 | defaultConfigurationName = Release; 628 | }; 629 | 37FE0DD924280B2300B95287 /* Build configuration list for PBXNativeTarget "Warhol" */ = { 630 | isa = XCConfigurationList; 631 | buildConfigurations = ( 632 | 37FE0DDA24280B2300B95287 /* Debug */, 633 | 37FE0DDB24280B2300B95287 /* Release */, 634 | ); 635 | defaultConfigurationIsVisible = 0; 636 | defaultConfigurationName = Release; 637 | }; 638 | 37FE0E1B2428C8D100B95287 /* Build configuration list for PBXNativeTarget "SampleApp" */ = { 639 | isa = XCConfigurationList; 640 | buildConfigurations = ( 641 | 37FE0E1C2428C8D100B95287 /* Debug */, 642 | 37FE0E1D2428C8D100B95287 /* Release */, 643 | ); 644 | defaultConfigurationIsVisible = 0; 645 | defaultConfigurationName = Release; 646 | }; 647 | /* End XCConfigurationList section */ 648 | }; 649 | rootObject = 37FE0DC824280B2200B95287 /* Project object */; 650 | } 651 | -------------------------------------------------------------------------------- /Warhol/Warhol.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Warhol/Warhol.xcodeproj/project.xcworkspace/xcuserdata/cesarvargas.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Warhol/a52a7248ac5fa1a45290959044d6cb0e0a1d667e/Warhol/Warhol.xcodeproj/project.xcworkspace/xcuserdata/cesarvargas.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Warhol/Warhol.xcodeproj/xcuserdata/cesarvargas.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SampleApp.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | Warhol.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Warhol/Warhol/CameraFaceDetectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WarholCameraViewController.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 22.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import AVFoundation 10 | #if !os(macOS) 11 | import UIKit 12 | #endif 13 | import Vision 14 | 15 | /// Views to be updated when a face is detected should comply with this protocol 16 | public protocol CameraFrontView: UIView { 17 | var viewModel: FaceViewModel? { get set } 18 | } 19 | 20 | public protocol CameraFaceDetectionDelegate: class { 21 | func faceViewModelDidUpdate(_ viewModel: FaceViewModel) 22 | func errorDidHappenWhenUpdating(_ error: WarholError) 23 | } 24 | 25 | /// This view controller shows the camera and updates the front view when a face is detected 26 | public class CameraFaceDetectionViewController: UIViewController { 27 | public weak var delegate: CameraFaceDetectionDelegate? 28 | var sequenceHandler = VNSequenceRequestHandler() 29 | 30 | private var faceViewModel = FaceViewModel() 31 | private let faceDetector = FaceDetector() 32 | 33 | /// This view will be called to draw through the draw(_ rect: CGRect) function 34 | /// when the face is detected and the view model updated 35 | public var cameraFrontView: CameraFrontView? { 36 | willSet { 37 | guard let faceView = newValue else { 38 | return 39 | } 40 | 41 | addCameraFrontView(faceView) 42 | } 43 | } 44 | 45 | /// Use this property to pass your layout assigning an image to face features 46 | public var faceLayout: FaceLayout? { 47 | willSet { 48 | guard let faceLayout = newValue else { 49 | return 50 | } 51 | 52 | cameraFrontView = FaceLayoutCameraFrontView(layout: faceLayout) 53 | } 54 | } 55 | 56 | private func addCameraFrontView(_ frontView: UIView) { 57 | view.addSubview(frontView) 58 | view.adjustSubviewToEdges(subView: frontView) 59 | } 60 | 61 | let session = AVCaptureSession() 62 | var previewLayer: AVCaptureVideoPreviewLayer! 63 | 64 | let dataOutputQueue = DispatchQueue( 65 | label: "video data queue", 66 | qos: .userInitiated, 67 | attributes: [], 68 | autoreleaseFrequency: .workItem) 69 | 70 | var faceViewHidden = false 71 | 72 | override public func viewDidLoad() { 73 | super.viewDidLoad() 74 | configureCaptureSession() 75 | 76 | session.startRunning() 77 | } 78 | } 79 | 80 | // MARK: - Video Processing methods 81 | extension CameraFaceDetectionViewController { 82 | func configureCaptureSession() { 83 | connectCameraToCaptureSessionInput() 84 | addVideoDataOutputToSession() 85 | configurePreviewLayer() 86 | } 87 | 88 | private func connectCameraToCaptureSessionInput() { 89 | guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, 90 | for: .video, 91 | position: .front) else { 92 | fatalError("No front video camera available") 93 | } 94 | 95 | do { 96 | let cameraInput = try AVCaptureDeviceInput(device: camera) 97 | session.addInput(cameraInput) 98 | } catch { 99 | fatalError(error.localizedDescription) 100 | } 101 | } 102 | 103 | private func addVideoDataOutputToSession() { 104 | let videoOutput = AVCaptureVideoDataOutput() 105 | videoOutput.setSampleBufferDelegate(self, queue: dataOutputQueue) 106 | videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA] 107 | 108 | session.addOutput(videoOutput) 109 | 110 | let videoConnection = videoOutput.connection(with: .video) 111 | videoConnection?.videoOrientation = .portrait 112 | } 113 | 114 | private func configurePreviewLayer() { 115 | previewLayer = AVCaptureVideoPreviewLayer(session: session) 116 | previewLayer.videoGravity = .resizeAspectFill 117 | previewLayer.frame = view.bounds 118 | view.layer.insertSublayer(previewLayer, at: 0) 119 | } 120 | } 121 | 122 | extension CameraFaceDetectionViewController: AVCaptureVideoDataOutputSampleBufferDelegate { 123 | public func captureOutput(_ output: AVCaptureOutput, 124 | didOutput sampleBuffer: CMSampleBuffer, 125 | from connection: AVCaptureConnection) { 126 | guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { 127 | return 128 | } 129 | 130 | faceDetector.detectFace(from: imageBuffer, previewLayer: previewLayer, completion: {viewModel in 131 | self.delegate?.faceViewModelDidUpdate(self.faceViewModel) 132 | self.cameraFrontView?.viewModel = viewModel 133 | 134 | DispatchQueue.main.async { 135 | self.cameraFrontView?.setNeedsDisplay() 136 | } 137 | }, error: { error in 138 | self.delegate?.errorDidHappenWhenUpdating(error) 139 | }) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Warhol/Warhol/Extensions/AVCaptureVideoPreviewLayer+Landmarks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AVCaptureVideoPreviewLayer+Landmarks.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 23.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | 12 | extension AVCaptureVideoPreviewLayer { 13 | func convert(rect: CGRect) -> CGRect { 14 | let origin = layerPointConverted(fromCaptureDevicePoint: rect.origin) 15 | let size = layerPointConverted(fromCaptureDevicePoint: rect.size.cgPoint) 16 | 17 | return CGRect(origin: origin, size: size.cgSize) 18 | } 19 | 20 | func landmark(point: CGPoint, to rect: CGRect) -> CGPoint { 21 | let absolute = point.absolutePoint(in: rect) 22 | let converted = layerPointConverted(fromCaptureDevicePoint: absolute) 23 | 24 | return converted 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Warhol/Warhol/Extensions/CGRect+SizeFactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect+SizeFactor.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 02.05.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | extension CGRect { 13 | func increaseRect(byPercentageWidth percentageWidth: CGFloat, 14 | byPercentageHeight percentageHeight: CGFloat) -> CGRect { 15 | let startWidth = width 16 | let startHeight = height 17 | let adjustmentWidth = (startWidth * percentageWidth) / 2.0 18 | let adjustmentHeight = (startHeight * percentageHeight) / 2.0 19 | return insetBy(dx: -adjustmentWidth, dy: -adjustmentHeight) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Warhol/Warhol/Extensions/CoreGraphicsExtensions.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | 3 | func + (left: CGPoint, right: CGPoint) -> CGPoint { 4 | return CGPoint(x: left.x + right.x, y: left.y + right.y) 5 | } 6 | 7 | func - (left: CGPoint, right: CGPoint) -> CGPoint { 8 | return CGPoint(x: left.x - right.x, y: left.y - right.y) 9 | } 10 | 11 | func * (left: CGPoint, right: CGFloat) -> CGPoint { 12 | return CGPoint(x: left.x * right, y: left.y * right) 13 | } 14 | 15 | extension CGSize { 16 | var cgPoint: CGPoint { 17 | return CGPoint(x: width, y: height) 18 | } 19 | } 20 | 21 | extension CGPoint { 22 | var cgSize: CGSize { 23 | return CGSize(width: x, height: y) 24 | } 25 | 26 | func absolutePoint(in rect: CGRect) -> CGPoint { 27 | return CGPoint(x: x * rect.size.width, y: y * rect.size.height) + rect.origin 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Warhol/Warhol/Extensions/FaceLandmarkPerimeter+Rect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FaceLandmarkPerimeter+Rect.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 03.05.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | extension FaceLandmarkPerimeter { 13 | func rect() -> CGRect { 14 | let leftMost: CGFloat = self.reduce(CGFloat.greatestFiniteMagnitude) { Swift.min($0, $1.x) } 15 | let upMost = reduce(CGFloat.greatestFiniteMagnitude) { Swift.min($0, $1.y) } 16 | let rightMost = reduce(0) { Swift.max($0, $1.x) } 17 | let downMost = reduce(0) { Swift.max($0, $1.y) } 18 | 19 | let originalWidth = rightMost - leftMost 20 | let originalHeight = downMost - upMost 21 | 22 | return CGRect(x: leftMost, y: upMost, width: originalWidth, height: originalHeight) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Warhol/Warhol/Extensions/UIView+Constraints.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Constraints.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 31.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIView { 13 | func adjustSubviewToEdges(subView: UIView) { 14 | subView.translatesAutoresizingMaskIntoConstraints = false 15 | 16 | let horizontalConstraint = NSLayoutConstraint(item: subView, 17 | attribute: NSLayoutConstraint.Attribute.centerX, 18 | relatedBy: NSLayoutConstraint.Relation.equal, toItem: self, 19 | attribute: NSLayoutConstraint.Attribute.centerX, 20 | multiplier: 1, 21 | constant: 0) 22 | 23 | let verticalConstraint = NSLayoutConstraint(item: subView, 24 | attribute: NSLayoutConstraint.Attribute.centerY, 25 | relatedBy: NSLayoutConstraint.Relation.equal, 26 | toItem: self, 27 | attribute: NSLayoutConstraint.Attribute.centerY, 28 | multiplier: 1, 29 | constant: 0) 30 | 31 | let widthConstraint = NSLayoutConstraint(item: subView, 32 | attribute: NSLayoutConstraint.Attribute.width, 33 | relatedBy: NSLayoutConstraint.Relation.equal, 34 | toItem: self, 35 | attribute: NSLayoutConstraint.Attribute.width, 36 | multiplier: 1, 37 | constant: 0) 38 | 39 | let heightConstraint = NSLayoutConstraint(item: subView, 40 | attribute: NSLayoutConstraint.Attribute.height, 41 | relatedBy: NSLayoutConstraint.Relation.equal, 42 | toItem: self, 43 | attribute: NSLayoutConstraint.Attribute.height, 44 | multiplier: 1, 45 | constant: 0) 46 | 47 | addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint]) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Warhol/Warhol/FaceDetector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FaceDetector.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 29.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Vision 11 | import UIKit 12 | import AVFoundation 13 | import CoreMedia 14 | 15 | class FaceDetector { 16 | func detectFace(from imageBuffer: CVImageBuffer, 17 | previewLayer: AVCaptureVideoPreviewLayer, 18 | completion: @escaping (FaceViewModel) -> Void, 19 | error: @escaping (WarholError) -> Void) { 20 | let detectFaceRequest = VNDetectFaceLandmarksRequest(completionHandler: { request, detectedError in 21 | if let detectedError = detectedError { 22 | error(WarholError.internalError(detectedError)) 23 | return 24 | } 25 | 26 | guard let results = request.results as? [VNFaceObservation], 27 | let result = results.first else { 28 | error(WarholError.noFaceDetected) 29 | return 30 | } 31 | 32 | let landmarkMaker: (VNFaceLandmarkRegion2D?) -> FaceLandmarkPerimeter? = { faceLandmarkRegion in 33 | guard let points = faceLandmarkRegion?.normalizedPoints else { 34 | return nil 35 | } 36 | 37 | return points.compactMap { previewLayer.landmark(point: $0, to: result.boundingBox) } 38 | } 39 | 40 | if let faceViewModel = FaceViewModel.faceViewModel(from: result, 41 | landmarkMaker: landmarkMaker, 42 | boundingBoxMaker: previewLayer.convert) { 43 | completion(faceViewModel) 44 | } 45 | }) 46 | 47 | let sequenceHandler = VNSequenceRequestHandler() 48 | 49 | do { 50 | try sequenceHandler.perform( 51 | [detectFaceRequest], 52 | on: imageBuffer, 53 | orientation: .leftMirrored) 54 | } catch { 55 | print(error.localizedDescription) 56 | } 57 | } 58 | 59 | private func detectFace(from image: UIImage, 60 | completion: @escaping (FaceViewModel) -> Void, 61 | error: @escaping (WarholError) -> Void) { 62 | let detectFaceRequest = VNDetectFaceLandmarksRequest { (request, detectedError) in 63 | if detectedError == nil { 64 | if let results = request.results as? [VNFaceObservation], 65 | let result = results.first { 66 | 67 | let rectWidth = image.size.width * result.boundingBox.size.width 68 | let rectHeight = image.size.height * result.boundingBox.size.height 69 | 70 | let boundingBoxMaker: (CGRect) -> CGRect = { boundingBox in 71 | return CGRect(x: boundingBox.origin.x * image.size.width, 72 | y: boundingBox.origin.y * image.size.height, 73 | width: rectWidth, 74 | height: rectHeight) 75 | } 76 | 77 | let landmarkMaker: (VNFaceLandmarkRegion2D?) -> FaceLandmarkPerimeter? = { faceLandmarkRegion in 78 | faceLandmarkRegion?.pointsInImage(imageSize: image.size) 79 | } 80 | 81 | if let viewModel = FaceViewModel.faceViewModel(from: result, 82 | landmarkMaker: landmarkMaker, 83 | boundingBoxMaker: boundingBoxMaker) { 84 | completion(viewModel) 85 | } 86 | } else { 87 | error(WarholError.noFaceDetected) 88 | } 89 | } else { 90 | print(detectedError!.localizedDescription) 91 | error(WarholError.internalError(detectedError!)) 92 | } 93 | } 94 | 95 | let vnImage = VNImageRequestHandler(cgImage: image.cgImage!, options: [:]) 96 | try? vnImage.perform([detectFaceRequest]) 97 | } 98 | 99 | func drawLandmarks(in imageView: UIImageView, 100 | draw: @escaping (FaceViewModel, CGContext) -> Void, 101 | error: @escaping (Error) -> Void) { 102 | guard let image = imageView.image else { 103 | error(WarholError.noImagePassed) 104 | return 105 | } 106 | 107 | detectFace(from: image, completion: { viewModel in 108 | UIGraphicsBeginImageContextWithOptions(image.size, false, 1) 109 | let context = UIGraphicsGetCurrentContext()! 110 | self.prepareContextForDrawing(context, image: image) 111 | 112 | let rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height) 113 | context.draw(image.cgImage!, in: rect) 114 | 115 | draw(viewModel, context) 116 | 117 | let processedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! 118 | UIGraphicsEndImageContext() 119 | 120 | imageView.image = processedImage 121 | }, error: error) 122 | } 123 | 124 | func drawLandmarksInNewImage(from imageView: UIImageView, 125 | draw: @escaping (FaceViewModel, CGContext) -> Void, 126 | completion: @escaping (UIImage) -> Void, 127 | error: @escaping (Error) -> Void) { 128 | guard let image = imageView.image else { 129 | error(WarholError.noImagePassed) 130 | return 131 | } 132 | 133 | detectFace(from: image, completion: { viewModel in 134 | UIGraphicsBeginImageContextWithOptions(image.size, false, 1) 135 | let context = UIGraphicsGetCurrentContext()! 136 | self.prepareContextForDrawing(context, image: image) 137 | 138 | let rect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height) 139 | context.draw(image.cgImage!, in: rect) 140 | 141 | draw(viewModel, context) 142 | 143 | let processedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! 144 | UIGraphicsEndImageContext() 145 | 146 | completion(processedImage) 147 | }, error: error) 148 | } 149 | 150 | private func prepareContextForDrawing(_ context: CGContext, image: UIImage) { 151 | context.translateBy(x: 0, y: image.size.height) 152 | context.scaleBy(x: 1.0, y: -1.0) 153 | context.setBlendMode(CGBlendMode.colorBurn) 154 | context.setLineJoin(.round) 155 | context.setLineCap(.round) 156 | context.setShouldAntialias(true) 157 | context.setAllowsAntialiasing(true) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Warhol/Warhol/FaceImagesLayout/FaceImagesLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FaceImagesLayout.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 01.05.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public struct SizeRatio { 13 | let width: CGFloat 14 | let height: CGFloat 15 | 16 | public init(width: CGFloat, height: CGFloat) { 17 | self.width = width 18 | self.height = height 19 | } 20 | } 21 | 22 | public struct ImageLayout { 23 | let image: UIImage 24 | let offset: CGPoint 25 | let sizeRatio: SizeRatio 26 | 27 | public init(image: UIImage, offset: CGPoint? = nil, sizeRatio: SizeRatio? = nil) { 28 | self.image = image 29 | self.offset = offset ?? CGPoint.zero 30 | self.sizeRatio = sizeRatio ?? SizeRatio(width: 1, height: 1) 31 | } 32 | } 33 | 34 | public struct FaceLayout { 35 | let landmarkLayouts: [FaceLandmarkType: ImageLayout] 36 | 37 | public init (landmarkLayouts: [FaceLandmarkType: ImageLayout]) { 38 | self.landmarkLayouts = landmarkLayouts 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Warhol/Warhol/FaceImagesLayout/FaceLayoutCameraFrontView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FaceLayoutCameraFrontView.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 01.05.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final class FaceLayoutCameraFrontView: UIView, CameraFrontView { 13 | var viewModel: FaceViewModel? 14 | var layout: FaceLayout 15 | 16 | init(layout: FaceLayout) { 17 | self.layout = layout 18 | 19 | super.init(frame: CGRect.zero) 20 | 21 | backgroundColor = .clear 22 | } 23 | 24 | required init?(coder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | override func draw(_ rect: CGRect) { 29 | guard let context = UIGraphicsGetCurrentContext(), 30 | let viewModel = viewModel else { 31 | return 32 | } 33 | 34 | context.saveGState() 35 | 36 | defer { 37 | context.restoreGState() 38 | } 39 | 40 | Array(viewModel.landmarks.keys).forEach { key in 41 | guard let imageLayout = layout.landmarkLayouts[key], 42 | let perimeter = viewModel.landmarks[key] else { 43 | return 44 | } 45 | 46 | draw(layout: imageLayout, in: context, from: perimeter) 47 | } 48 | } 49 | 50 | private func draw(layout: ImageLayout, in context: CGContext, from perimeter: FaceLandmarkPerimeter) { 51 | let offsetAdjustedRect = perimeter.rect().offsetBy(dx: layout.offset.x, dy: layout.offset.y) 52 | 53 | layout.image.draw(in: offsetAdjustedRect.increaseRect(byPercentageWidth: layout.sizeRatio.width, 54 | byPercentageHeight: layout.sizeRatio.height)) 55 | //context.draw(layout.image.cgImage!, in: rect.increaseRect(byPercentageWidth: 1, byPercentageHeight: 4)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Warhol/Warhol/FaceViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Warholswift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 23.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import Vision 12 | 13 | public typealias FaceLandmarkPerimeter = [CGPoint] 14 | 15 | public struct FaceViewModel { 16 | internal(set) public var landmarks: [FaceLandmarkType: FaceLandmarkPerimeter] 17 | 18 | internal(set) public var boundingBox = CGRect.zero 19 | 20 | init() { 21 | landmarks = [:] 22 | } 23 | 24 | static func faceViewModel(from faceObservation: VNFaceObservation, 25 | landmarkMaker: (VNFaceLandmarkRegion2D?) -> FaceLandmarkPerimeter?, 26 | boundingBoxMaker: (CGRect) -> CGRect) -> FaceViewModel? { 27 | guard let landmarks = faceObservation.landmarks else { 28 | return nil 29 | } 30 | 31 | var viewModel = FaceViewModel() 32 | 33 | viewModel.boundingBox = boundingBoxMaker(faceObservation.boundingBox) 34 | 35 | if let leftEye = landmarkMaker(landmarks.leftEye) { 36 | viewModel.landmarks[.leftEye] = leftEye 37 | } 38 | 39 | if let rightEye = landmarkMaker(landmarks.rightEye) { 40 | viewModel.landmarks[.rightEye] = rightEye 41 | } 42 | 43 | if let leftEyebrow = landmarkMaker(landmarks.leftEyebrow) { 44 | viewModel.landmarks[.leftEyebrow] = leftEyebrow 45 | } 46 | 47 | if let rightEyebrow = landmarkMaker(landmarks.rightEyebrow) { 48 | viewModel.landmarks[.rightEyebrow] = rightEyebrow 49 | } 50 | 51 | if let nose = landmarkMaker(landmarks.nose) { 52 | viewModel.landmarks[.nose] = nose 53 | } 54 | 55 | if let outerLips = landmarkMaker(landmarks.outerLips) { 56 | viewModel.landmarks[.outerLips] = outerLips 57 | } 58 | 59 | if let innerLips = landmarkMaker(landmarks.innerLips) { 60 | viewModel.landmarks[.innerLips] = innerLips 61 | } 62 | 63 | if let faceContour = landmarkMaker(landmarks.faceContour) { 64 | viewModel.landmarks[.faceContour] = faceContour 65 | } 66 | 67 | return viewModel 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Warhol/Warhol/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Warhol/Warhol/Warhol.h: -------------------------------------------------------------------------------- 1 | // 2 | // Warhol.h 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 22.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Warhol. 12 | FOUNDATION_EXPORT double WarholVersionNumber; 13 | 14 | //! Project version string for Warhol. 15 | FOUNDATION_EXPORT const unsigned char WarholVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Warhol/Warhol/Warhol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Warhol.swift 3 | // Warhol 4 | // 5 | // Created by Cesar Vargas on 22.03.20. 6 | // Copyright © 2020 Cesar Vargas. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Vision 11 | 12 | public enum WarholError: Error { 13 | case noImagePassed 14 | case noFaceDetected 15 | case internalError(Error) 16 | } 17 | 18 | public enum FaceLandmarkType { 19 | case leftEye 20 | case rightEye 21 | case leftEyebrow 22 | case rightEyebrow 23 | case nose 24 | case outerLips 25 | case innerLips 26 | case faceContour 27 | } 28 | 29 | /** 30 | Detects a face given a UIImageView instance, and asks the caller to draw on it. 31 | 32 | - Parameter imageView: The image view containing the face to be detected. 33 | - Parameter draw: This closure is called when the face is detected. 34 | The caller is asked to draw given the detected view model and a context 35 | - Parameter error: Called when an error is found 36 | */ 37 | public func drawLandmarks(in imageView: UIImageView, 38 | draw: @escaping (FaceViewModel, CGContext) -> Void, 39 | error: @escaping (Error) -> Void) { 40 | let faceDetector = FaceDetector() 41 | faceDetector.drawLandmarks(in: imageView, draw: draw, error: error) 42 | } 43 | 44 | /** 45 | Detects a face given a UIImageView instance, and asks the caller to draw generating a new one. 46 | 47 | - Parameter imageView: The image view containing the face to be detected. 48 | - Parameter draw: This closure is called when the face is detected. 49 | The caller is asked to draw given the detected view model and a context 50 | - Parameter completion: Called when the new image has been generated. 51 | It is composed of the original image in the background and the caller drawing on top. 52 | - Parameter error: Called when an error is found 53 | */ 54 | public func drawLandmarksInNewImage(from imageView: UIImageView, 55 | draw: @escaping (FaceViewModel, CGContext) -> Void, 56 | completion: @escaping (UIImage) -> Void, 57 | error: @escaping (Error) -> Void) { 58 | let faceDetector = FaceDetector() 59 | faceDetector.drawLandmarksInNewImage(from: imageView, draw: draw, completion: completion, error: error) 60 | } 61 | -------------------------------------------------------------------------------- /warhol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toupper/Warhol/a52a7248ac5fa1a45290959044d6cb0e0a1d667e/warhol.png --------------------------------------------------------------------------------