├── .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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
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 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
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
--------------------------------------------------------------------------------