├── .gitignore ├── Maydayfile ├── Project.xcconfig ├── README.md ├── THVideoFaceSwapper.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── THVideoFaceSwapper.xcscheme ├── THVideoFaceSwapper.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── THVideoFaceSwapper.xcscmblueprint │ └── xcschemes │ └── THVideoFaceSwapper.xcscheme ├── addons.make ├── bin └── data │ ├── Default-568h@2x~iphone.png │ ├── Default.png │ ├── Default@2x.png │ ├── Default@2x~ipad.png │ ├── Default@2x~iphone.png │ ├── Default~ipad.png │ ├── Default~iphone.png │ ├── Icon-72.png │ ├── Icon-72@2x.png │ ├── Icon.png │ ├── Icon@2x.png │ ├── faces │ ├── 1535.jpg │ ├── 95662.jpeg │ ├── Barack_Obama,_official_photo_portrait,_111th_Congress.jpg │ ├── Emma-Watson_2967465b.jpg │ ├── Steve-Jobs-Stamp.jpg │ ├── beard.jpg │ ├── hairstyles-for-men-with-round-faces-best.jpg │ ├── lebron-james-basketball-headshot-photo.jpg │ ├── photo.jpg │ └── pict.php.jpeg │ ├── model │ ├── face.con │ ├── face.tracker │ ├── face.tri │ └── face2.tracker │ └── shader │ ├── cloneShader.frag │ ├── cloneShader.vert │ ├── maskBlurShader.frag │ └── maskBlurShader.vert ├── ofxiOS-Info.plist ├── ofxiOS_Prefix.pch ├── openFrameworks ├── Project.xcconfig ├── ofxiOS-Info.plist └── ofxiOS_Prefix.pch └── src ├── Categories ├── UIImage+Decode.h └── UIImage+Decode.m ├── Data ├── THFacesCollectionViewDataSource.h └── THFacesCollectionViewDataSource.mm ├── Models ├── Clone.cpp └── Clone.h ├── Resources ├── Default-568h@2x~iphone.png ├── Default.png ├── Default@2x.png ├── Icon.png ├── Icon@2x.png └── MediaAssets.xcassets │ ├── camera.imageset │ ├── Camera-100.png │ ├── Camera-64.png │ └── Contents.json │ └── close.imageset │ ├── Close-100.png │ ├── Close-64.png │ └── Contents.json ├── View Controllers ├── THPhotoPickerViewController.h └── THPhotoPickerViewController.mm ├── Views ├── Loading │ ├── MBProgressHUD.h │ └── MBProgressHUD.m ├── THFacePickerCollectionViewCell.h ├── THFacePickerCollectionViewCell.m ├── THFacesCollectionReusableView.h └── THFacesCollectionReusableView.m ├── main.mm ├── ofApp.h └── ofApp.mm /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | *.DS_Store 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | # CocoaPods 22 | # 23 | # We recommend against adding the Pods directory to your .gitignore. However 24 | # you should judge for yourself, the pros and cons are mentioned at: 25 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 26 | # 27 | #Pods/ 28 | -------------------------------------------------------------------------------- /Maydayfile: -------------------------------------------------------------------------------- 1 | xcode_proj 'THVideoFaceSwapper.xcodeproj' 2 | 3 | warning_regex 'TODO:', /\s+\/\/\s?TODO:/ 4 | -------------------------------------------------------------------------------- /Project.xcconfig: -------------------------------------------------------------------------------- 1 | //THE PATH TO THE ROOT OF OUR OF PATH RELATIVE TO THIS PROJECT. 2 | //THIS NEEDS TO BE DEFINED BEFORE CoreOF.xcconfig IS INCLUDED 3 | OF_PATH = ../../.. 4 | 5 | //THIS HAS ALL THE HEADER AND LIBS FOR OF CORE 6 | #include "../../../libs/openFrameworksCompiled/project/ios/CoreOF.xcconfig" 7 | 8 | OTHER_LDFLAGS = $(OF_CORE_LIBS) 9 | HEADER_SEARCH_PATHS = $(OF_CORE_HEADERS) 10 | 11 | ENABLE_BITCODE = NO 12 | COMPRESS_PNG_FILES = NO 13 | GCC_THUMB_SUPPORT = NO 14 | IPHONEOS_DEPLOYMENT_TARGET = 5.1.1 15 | TARGETED_DEVICE_FAMILY = 1 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THVideoFaceSwapper 2 | Live video face swapping on iOS. Choose a face from the preloaded faces, or take one yourself! 3 | 4 | ![Demo](http://i.imgur.com/ERkEh9e.gif) 5 | 6 | #Building and Running the App 7 | The header search path for the openFramework libs should 3 levels up from this project (`../../..`). Saving this repo within `~/path/to/openFrameworks/apps/myApps/` and building and running the app will most likely work unless you've configured your openFrameoworks directory differently. 8 | 9 | If you want to save you this within a different directory that is not 3 levels below the openFrameworks root, then you will need to change the `OF_PATH` in the `Project.xcconfig` (not recommended). 10 | 11 | #Contributing 12 | Only alter the `src` Group within this project. If you add new Groups and/or files, please follow the directions below. 13 | 14 | The file structure for this project is maintained via [synx](https://github.com/venmo/synx). It mirrors the abstract Xcode file structure in Finder. When using on this project, there are a number of Groups to exclude due to openFrameworks needing an unaltered file structure. Once you've made your changes within the `src` folder, run `synx -e /openFrameworks -e /addons -e /libs -e /Products THVideoFaceSwapper.xcodeproj/` 15 | This will exclude all of the openFramework Groups. 16 | 17 | #Other Notes 18 | ###Adding Faces 19 | Faces go into `bin/data/faces/` as a JPG, JPEG or PNG 20 | 21 | ###Maydayfile 22 | Adds supplemental warnings and errors to your Xcode project via regex. For more info see [mayday](https://github.com/marklarr/mayday) 23 | -------------------------------------------------------------------------------- /THVideoFaceSwapper.xcodeproj/xcshareddata/xcschemes/THVideoFaceSwapper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /THVideoFaceSwapper.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /THVideoFaceSwapper.xcworkspace/xcshareddata/THVideoFaceSwapper.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "8852804AF21CBEE8B8D43E407C5315E4D57E430A", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "37526DA425E5430474A14FBFA1BC4525160B5FC1" : 0, 8 | "A4D0373C699384D3146D509694B516AB9C7B5AD6" : 0, 9 | "8852804AF21CBEE8B8D43E407C5315E4D57E430A" : 0 10 | }, 11 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "E655A168-ACDA-4C10-BC7D-E16EAAF3DC54", 12 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 13 | "37526DA425E5430474A14FBFA1BC4525160B5FC1" : "..\/..\/addons\/ofxCv", 14 | "A4D0373C699384D3146D509694B516AB9C7B5AD6" : "..\/..\/addons\/ofxFaceTracker", 15 | "8852804AF21CBEE8B8D43E407C5315E4D57E430A" : "THVideoFaceSwapper\/" 16 | }, 17 | "DVTSourceControlWorkspaceBlueprintNameKey" : "THVideoFaceSwapper", 18 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 19 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "THVideoFaceSwapper.xcworkspace", 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 21 | { 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:kylemcdonald\/ofxCv.git", 23 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 24 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "37526DA425E5430474A14FBFA1BC4525160B5FC1" 25 | }, 26 | { 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:thehackerati\/THVideoFaceSwapper.git", 28 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 29 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8852804AF21CBEE8B8D43E407C5315E4D57E430A" 30 | }, 31 | { 32 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:kylemcdonald\/ofxFaceTracker.git", 33 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 34 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "A4D0373C699384D3146D509694B516AB9C7B5AD6" 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /THVideoFaceSwapper.xcworkspace/xcshareddata/xcschemes/THVideoFaceSwapper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /addons.make: -------------------------------------------------------------------------------- 1 | ofxCv 2 | ofxFaceTracker 3 | ofxOpenCv 4 | -------------------------------------------------------------------------------- /bin/data/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /bin/data/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Default.png -------------------------------------------------------------------------------- /bin/data/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Default@2x.png -------------------------------------------------------------------------------- /bin/data/Default@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Default@2x~ipad.png -------------------------------------------------------------------------------- /bin/data/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Default@2x~iphone.png -------------------------------------------------------------------------------- /bin/data/Default~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Default~ipad.png -------------------------------------------------------------------------------- /bin/data/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Default~iphone.png -------------------------------------------------------------------------------- /bin/data/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Icon-72.png -------------------------------------------------------------------------------- /bin/data/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Icon-72@2x.png -------------------------------------------------------------------------------- /bin/data/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Icon.png -------------------------------------------------------------------------------- /bin/data/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/Icon@2x.png -------------------------------------------------------------------------------- /bin/data/faces/1535.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/1535.jpg -------------------------------------------------------------------------------- /bin/data/faces/95662.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/95662.jpeg -------------------------------------------------------------------------------- /bin/data/faces/Barack_Obama,_official_photo_portrait,_111th_Congress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/Barack_Obama,_official_photo_portrait,_111th_Congress.jpg -------------------------------------------------------------------------------- /bin/data/faces/Emma-Watson_2967465b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/Emma-Watson_2967465b.jpg -------------------------------------------------------------------------------- /bin/data/faces/Steve-Jobs-Stamp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/Steve-Jobs-Stamp.jpg -------------------------------------------------------------------------------- /bin/data/faces/beard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/beard.jpg -------------------------------------------------------------------------------- /bin/data/faces/hairstyles-for-men-with-round-faces-best.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/hairstyles-for-men-with-round-faces-best.jpg -------------------------------------------------------------------------------- /bin/data/faces/lebron-james-basketball-headshot-photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/lebron-james-basketball-headshot-photo.jpg -------------------------------------------------------------------------------- /bin/data/faces/photo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/photo.jpg -------------------------------------------------------------------------------- /bin/data/faces/pict.php.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/bin/data/faces/pict.php.jpeg -------------------------------------------------------------------------------- /bin/data/model/face.con: -------------------------------------------------------------------------------- 1 | n_connections: 61 2 | { 3 | 0 1 4 | 1 2 5 | 2 3 6 | 3 4 7 | 4 5 8 | 5 6 9 | 6 7 10 | 7 8 11 | 8 9 12 | 9 10 13 | 10 11 14 | 11 12 15 | 12 13 16 | 13 14 17 | 14 15 18 | 15 16 19 | 17 18 20 | 18 19 21 | 19 20 22 | 20 21 23 | 22 23 24 | 23 24 25 | 24 25 26 | 25 26 27 | 27 28 28 | 28 29 29 | 29 30 30 | 31 32 31 | 32 33 32 | 33 34 33 | 34 35 34 | 36 37 35 | 37 38 36 | 38 39 37 | 39 40 38 | 40 41 39 | 41 36 40 | 42 43 41 | 43 44 42 | 44 45 43 | 45 46 44 | 46 47 45 | 47 42 46 | 48 49 47 | 49 50 48 | 50 51 49 | 51 52 50 | 52 53 51 | 53 54 52 | 54 55 53 | 55 56 54 | 56 57 55 | 57 58 56 | 58 59 57 | 59 48 58 | 60 65 59 | 60 61 60 | 61 62 61 | 62 63 62 | 63 64 63 | 64 65 64 | } 65 | -------------------------------------------------------------------------------- /bin/data/model/face.tri: -------------------------------------------------------------------------------- 1 | n_tri: 91 2 | { 3 | 20 21 23 4 | 21 22 23 5 | 0 1 36 6 | 15 16 45 7 | 0 17 36 8 | 16 26 45 9 | 17 18 37 10 | 25 26 44 11 | 17 36 37 12 | 26 44 45 13 | 18 19 38 14 | 24 25 43 15 | 18 37 38 16 | 25 43 44 17 | 19 20 38 18 | 23 24 43 19 | 20 21 39 20 | 22 23 42 21 | 20 38 39 22 | 23 42 43 23 | 21 22 27 24 | 21 27 39 25 | 22 27 42 26 | 27 28 42 27 | 27 28 39 28 | 28 42 47 29 | 28 39 40 30 | 1 36 41 31 | 15 45 46 32 | 1 2 41 33 | 14 15 46 34 | 28 29 40 35 | 28 29 47 36 | 2 40 41 37 | 14 46 47 38 | 2 29 40 39 | 14 29 47 40 | 2 3 29 41 | 13 14 29 42 | 29 30 31 43 | 29 30 35 44 | 3 29 31 45 | 13 29 35 46 | 30 32 33 47 | 30 33 34 48 | 30 31 32 49 | 30 34 35 50 | 3 4 31 51 | 12 13 35 52 | 4 5 48 53 | 11 12 54 54 | 5 6 48 55 | 10 11 54 56 | 6 48 59 57 | 10 54 55 58 | 6 7 59 59 | 9 10 55 60 | 7 58 59 61 | 9 55 56 62 | 8 57 58 63 | 8 56 57 64 | 7 8 58 65 | 8 9 56 66 | 4 31 48 67 | 12 35 54 68 | 31 48 49 69 | 35 53 54 70 | 31 49 50 71 | 35 52 53 72 | 31 32 50 73 | 34 35 52 74 | 32 33 50 75 | 33 34 52 76 | 33 50 51 77 | 33 51 52 78 | 48 49 60 79 | 49 60 50 80 | 50 60 61 81 | 50 51 61 82 | 51 52 61 83 | 61 62 52 84 | 52 53 62 85 | 53 54 62 86 | 54 55 63 87 | 55 56 63 88 | 56 63 64 89 | 56 57 64 90 | 64 65 57 91 | 57 58 65 92 | 58 59 65 93 | 48 59 65 94 | } 95 | -------------------------------------------------------------------------------- /bin/data/shader/cloneShader.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D src, srcBlur, dstBlur; 4 | 5 | varying vec2 texCoordVarying; 6 | 7 | void main() { 8 | vec2 pos = vec2(texCoordVarying.x, texCoordVarying.y); 9 | vec4 srcColorBlur = texture2D(srcBlur, pos); 10 | if(srcColorBlur.a > 0.){ 11 | 12 | vec3 srcColor = texture2D(src, pos).rgb; 13 | vec4 dstColorBlur = texture2D(dstBlur, pos); 14 | vec3 offset = dstColorBlur.rgb - srcColorBlur.rgb; 15 | 16 | gl_FragColor = vec4(srcColor + offset, 1.); 17 | 18 | } 19 | else{ 20 | 21 | gl_FragColor = vec4(0.); 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /bin/data/shader/cloneShader.vert: -------------------------------------------------------------------------------- 1 | // these are for the programmable pipeline system 2 | uniform mat4 modelViewProjectionMatrix; 3 | attribute vec4 position; 4 | attribute vec2 texcoord; 5 | 6 | // this is something we're creating for this shader 7 | varying vec2 texCoordVarying; 8 | 9 | 10 | void main() 11 | { 12 | 13 | texCoordVarying = texcoord; 14 | 15 | gl_Position = modelViewProjectionMatrix * position; 16 | 17 | 18 | } -------------------------------------------------------------------------------- /bin/data/shader/maskBlurShader.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | uniform sampler2D tex0, mask; 4 | uniform vec2 direction; 5 | uniform int k; 6 | 7 | varying vec2 texCoordVarying; 8 | 9 | void main() { 10 | 11 | vec2 pos = vec2(texCoordVarying.x, texCoordVarying.y); //gl_TexCoord[0].st; 12 | vec4 sum = texture2D(tex0, pos); 13 | int i; 14 | for(i=1;i= 0.5 && mask3.r >= 0.5){ 25 | 26 | 27 | sum += texture2D(tex0, pos + curOffset) + texture2D(tex0, pos - curOffset); 28 | 29 | } 30 | else{ 31 | 32 | break; 33 | 34 | } 35 | } 36 | int samples = 1 + (i-1)*2; 37 | sum /= float(samples); 38 | 39 | gl_FragColor = sum; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /bin/data/shader/maskBlurShader.vert: -------------------------------------------------------------------------------- 1 | // these are for the programmable pipeline system 2 | uniform mat4 modelViewProjectionMatrix; 3 | attribute vec4 position; 4 | attribute vec2 texcoord; 5 | 6 | // this is something we're creating for this shader 7 | varying vec2 texCoordVarying; 8 | 9 | 10 | void main() 11 | { 12 | 13 | texCoordVarying = texcoord; 14 | 15 | gl_Position = modelViewProjectionMatrix * position; 16 | 17 | 18 | } -------------------------------------------------------------------------------- /ofxiOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIStatusBarHidden 6 | 7 | UIViewControllerBasedStatusBarAppearance 8 | 9 | CFBundleDevelopmentRegion 10 | English 11 | CFBundleDisplayName 12 | ${PRODUCT_NAME} 13 | CFBundleExecutable 14 | ${EXECUTABLE_NAME} 15 | CFBundleIconFile 16 | 17 | CFBundleIcons 18 | 19 | CFBundlePrimaryIcon 20 | 21 | CFBundleIconFiles 22 | 23 | Icon.png 24 | Icon@2x.png 25 | 26 | 27 | 28 | CFBundleIdentifier 29 | ${PRODUCT_NAME:identifier} 30 | CFBundleInfoDictionaryVersion 31 | 6.0 32 | CFBundleName 33 | ${PRODUCT_NAME} 34 | CFBundlePackageType 35 | APPL 36 | CFBundleShortVersionString 37 | 1.0 38 | CFBundleSignature 39 | ???? 40 | CFBundleVersion 41 | 1.0 42 | LSRequiresIPhoneOS 43 | 44 | UIApplicationExitsOnSuspend 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ofxiOS_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'iPhone' target in the 'iPhone' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /openFrameworks/Project.xcconfig: -------------------------------------------------------------------------------- 1 | //THE PATH TO THE ROOT OF OUR OF PATH RELATIVE TO THIS PROJECT. 2 | //THIS NEEDS TO BE DEFINED BEFORE CoreOF.xcconfig IS INCLUDED 3 | OF_PATH = ../../.. 4 | 5 | //THIS HAS ALL THE HEADER AND LIBS FOR OF CORE 6 | #include "../../../libs/openFrameworksCompiled/project/ios/CoreOF.xcconfig" 7 | 8 | OTHER_LDFLAGS = $(OF_CORE_LIBS) 9 | HEADER_SEARCH_PATHS = $(OF_CORE_HEADERS) 10 | 11 | COMPRESS_PNG_FILES = NO 12 | GCC_THUMB_SUPPORT = NO 13 | IPHONEOS_DEPLOYMENT_TARGET = 3.1 14 | TARGETED_DEVICE_FAMILY = 1 15 | -------------------------------------------------------------------------------- /openFrameworks/ofxiOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIStatusBarHidden 6 | 7 | UIViewControllerBasedStatusBarAppearance 8 | 9 | CFBundleDevelopmentRegion 10 | English 11 | CFBundleDisplayName 12 | ${PRODUCT_NAME} 13 | CFBundleExecutable 14 | ${EXECUTABLE_NAME} 15 | CFBundleIconFile 16 | 17 | CFBundleIcons 18 | 19 | CFBundlePrimaryIcon 20 | 21 | CFBundleIconFiles 22 | 23 | Icon.png 24 | Icon@2x.png 25 | 26 | 27 | 28 | CFBundleIdentifier 29 | ${PRODUCT_NAME:identifier} 30 | CFBundleInfoDictionaryVersion 31 | 6.0 32 | CFBundleName 33 | ${PRODUCT_NAME} 34 | CFBundlePackageType 35 | APPL 36 | CFBundleShortVersionString 37 | 1.0 38 | CFBundleSignature 39 | ???? 40 | CFBundleVersion 41 | 1.0 42 | LSRequiresIPhoneOS 43 | 44 | UIApplicationExitsOnSuspend 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /openFrameworks/ofxiOS_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'iPhone' target in the 'iPhone' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #import 8 | #endif 9 | -------------------------------------------------------------------------------- /src/Categories/UIImage+Decode.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Decode.h 3 | // THVideoFaceSwapper 4 | // 5 | // Created by Clayton Rieck on 4/22/15. 6 | // 7 | // 8 | 9 | @interface UIImage (Decode) 10 | 11 | - (UIImage *)decodedImage; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /src/Categories/UIImage+Decode.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Decode.m 3 | // THVideoFaceSwapper 4 | // 5 | // Created by Clayton Rieck on 4/22/15. 6 | // 7 | // 8 | 9 | #import "UIImage+Decode.h" 10 | 11 | @implementation UIImage (Decode) 12 | 13 | - (UIImage *)decodedImage 14 | { 15 | CGImageRef imageRef = self.CGImage; 16 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 17 | CGContextRef context = CGBitmapContextCreate(NULL, 18 | CGImageGetWidth(imageRef), 19 | CGImageGetHeight(imageRef), 20 | 8, 21 | // Just always return width * 4 will be enough 22 | CGImageGetWidth(imageRef) * 4, 23 | // System only supports RGB, set explicitly 24 | colorSpace, 25 | // Makes system don't need to do extra conversion when displayed. 26 | kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); 27 | CGColorSpaceRelease(colorSpace); 28 | if (!context) return nil; 29 | 30 | CGRect rect = (CGRect){CGPointZero,{static_cast(CGImageGetWidth(imageRef)), static_cast(CGImageGetHeight(imageRef))}}; 31 | CGContextDrawImage(context, rect, imageRef); 32 | CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); 33 | CGContextRelease(context); 34 | 35 | UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef scale:self.scale orientation:self.imageOrientation]; 36 | CGImageRelease(decompressedImageRef); 37 | return decompressedImage; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /src/Data/THFacesCollectionViewDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // THFacesCollectionViewDataSource.h 3 | // THVideoFaceSwapper 4 | // 5 | // Created by Clayton Rieck on 4/27/15. 6 | // 7 | // 8 | 9 | #include 10 | 11 | @interface THFacesCollectionViewDataSource : NSObject 12 | 13 | - (instancetype)initWithCollectionView:(UICollectionView *)collectionView; 14 | 15 | - (NSInteger)numberOfItemsToDelete; 16 | - (void)addIndexPathToDelete:(NSIndexPath *)indexPath; 17 | - (void)removeIndexPathToDelete:(NSIndexPath *)indexPath; 18 | - (void)deleteSelectedItems:(void (^)(void))completion; 19 | 20 | - (NSString *)savedImageNameAtIndexPath:(NSIndexPath *)indexPath; 21 | - (UIImage *)imageFromDocumentsDirectoryNamed:(NSString *)name; 22 | 23 | - (void)loadFace:(UIImage *)image saveImage:(BOOL)save withCompletion:(void (^)(void))completion; 24 | 25 | - (void)resetCellStates; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /src/Data/THFacesCollectionViewDataSource.mm: -------------------------------------------------------------------------------- 1 | // 2 | // THFacesCollectionViewDataSource.m 3 | // THVideoFaceSwapper 4 | // 5 | // Created by Clayton Rieck on 4/27/15. 6 | // 7 | // 8 | 9 | #include "ofApp.h" 10 | 11 | #import "THFacesCollectionViewDataSource.h" 12 | #import "THFacePickerCollectionViewCell.h" 13 | #import "THFacesCollectionReusableView.h" 14 | 15 | #import "UIImage+Decode.h" 16 | 17 | static NSString *kTHCellReuseIdentifier = @"cell"; 18 | static NSString *kTHSupplementaryHeaderViewReuseIdentifier = @"supplementaryHeaderView"; 19 | 20 | static NSString * const kTHDocumentsDirectoryPath = ofxStringToNSString(ofxiOSGetDocumentsDirectory()); 21 | static NSString * const kTHAlphanumericCharacters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 22 | static NSString * const kTHPreInstalledFacesHeaderTitle = @"Pre-Installed Faces"; 23 | static NSString * const kTHSavedFacesHeaderTitle = @"Saved Faces"; 24 | 25 | static const string kTHSavedImagesExtension = ".png"; 26 | 27 | @interface THFacesCollectionViewDataSource () 28 | { 29 | ofApp *mainApp; 30 | } 31 | 32 | @property (nonatomic) UICollectionView *collectionView; 33 | @property (nonatomic) NSMutableArray *savedFaces; 34 | @property (nonatomic) NSMutableSet *indexPathsToDelete; 35 | 36 | @end 37 | 38 | @implementation THFacesCollectionViewDataSource 39 | 40 | - (instancetype)initWithCollectionView:(UICollectionView *)collectionView 41 | { 42 | self = [super init]; 43 | if ( self ) { 44 | mainApp = (ofApp *)ofGetAppPtr(); 45 | _collectionView = collectionView; 46 | [_collectionView registerClass:[THFacePickerCollectionViewCell class] forCellWithReuseIdentifier:kTHCellReuseIdentifier]; 47 | [_collectionView registerClass:[THFacesCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kTHSupplementaryHeaderViewReuseIdentifier]; 48 | 49 | _savedFaces = (NSMutableArray *)[[[NSFileManager defaultManager] contentsOfDirectoryAtPath:kTHDocumentsDirectoryPath error:nil] mutableCopy]; 50 | _indexPathsToDelete = [[NSMutableSet alloc] init]; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)dealloc 56 | { 57 | _collectionView = nil; 58 | _savedFaces = nil; 59 | _indexPathsToDelete = nil; 60 | mainApp = NULL; 61 | } 62 | 63 | #pragma mark - Public 64 | 65 | - (NSInteger)numberOfItemsToDelete 66 | { 67 | return self.indexPathsToDelete.count; 68 | } 69 | 70 | - (void)addIndexPathToDelete:(NSIndexPath *)indexPath 71 | { 72 | if ( ![self.indexPathsToDelete containsObject:indexPath] ) { 73 | [self.indexPathsToDelete addObject:indexPath]; 74 | } 75 | } 76 | 77 | - (void)removeIndexPathToDelete:(NSIndexPath *)indexPath 78 | { 79 | if ( [self.indexPathsToDelete containsObject:indexPath] ) { 80 | [self.indexPathsToDelete removeObject:indexPath]; 81 | } 82 | } 83 | 84 | - (void)deleteSelectedItems:(void (^)(void))completion 85 | { 86 | [self.collectionView performBatchUpdates:^{ 87 | 88 | NSMutableArray *fileNamesToRemove = [NSMutableArray array]; 89 | NSError *err; 90 | for (NSIndexPath *path in self.indexPathsToDelete) { 91 | 92 | THFacePickerCollectionViewCell *cell = (THFacePickerCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:path]; 93 | [cell highlightSelected:NO]; 94 | 95 | [fileNamesToRemove addObject:[self.savedFaces objectAtIndex:path.row]]; 96 | NSString *fileToDelete = [kTHDocumentsDirectoryPath stringByAppendingPathComponent:[self.savedFaces objectAtIndex:path.row]]; 97 | 98 | if ( ![[NSFileManager defaultManager] removeItemAtPath:fileToDelete error:&err] ) { 99 | 100 | UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:@"File Save Error" 101 | message:err.localizedDescription 102 | delegate:nil 103 | cancelButtonTitle:@"Ok" 104 | otherButtonTitles:nil, nil]; 105 | [errorAlert show]; 106 | } 107 | } 108 | 109 | [self.savedFaces removeObjectsInArray:fileNamesToRemove]; 110 | [self.collectionView deleteItemsAtIndexPaths:[self.indexPathsToDelete allObjects]]; 111 | 112 | } completion:^(BOOL finished){ 113 | [self.indexPathsToDelete removeAllObjects]; 114 | if ( completion ) { 115 | completion(); 116 | } 117 | }]; 118 | } 119 | 120 | - (NSString *)savedImageNameAtIndexPath:(NSIndexPath *)indexPath 121 | { 122 | return self.savedFaces[indexPath.row]; 123 | } 124 | 125 | - (UIImage *)imageFromDocumentsDirectoryNamed:(NSString *)name 126 | { 127 | NSString *imagePath = [kTHDocumentsDirectoryPath stringByAppendingPathComponent:name]; 128 | return [UIImage imageWithContentsOfFile:imagePath]; 129 | } 130 | 131 | - (void)loadFace:(UIImage *)image saveImage:(BOOL)save withCompletion:(void (^)(void))completion 132 | { 133 | NSLog(@"LOADING FACE"); 134 | dispatch_async(dispatch_queue_create("imageLoadingQueue", NULL), ^{ 135 | 136 | ofImage pickedImage; 137 | ofxiOSUIImageToOFImage(image, pickedImage); 138 | pickedImage.rotate90(1); 139 | 140 | if ( save ) { 141 | NSString *newFileName = [self randomString]; 142 | while ( [self.savedFaces containsObject:newFileName] ) { // ensures that we'll never over write a previous image (highly unlikely anyways) 143 | newFileName = [self randomString]; 144 | } 145 | string cStringSavedFaceName = ofxNSStringToString(newFileName) + kTHSavedImagesExtension; 146 | // Save image to documents directory 147 | pickedImage.saveImage(ofxiOSGetDocumentsDirectory() + cStringSavedFaceName); 148 | [self.savedFaces addObject:ofxStringToNSString(cStringSavedFaceName)]; 149 | } 150 | 151 | pickedImage.setImageType(OF_IMAGE_COLOR); // set the type AFTER we save image to documents 152 | 153 | dispatch_async(dispatch_get_main_queue(), ^{ 154 | 155 | mainApp->loadOFImage(pickedImage); 156 | mainApp->setupCam([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); 157 | 158 | [self.collectionView reloadSections:[[NSIndexSet alloc] initWithIndex:1]]; 159 | 160 | if ( completion ) { 161 | completion(); 162 | } 163 | }); 164 | }); 165 | } 166 | 167 | - (void)resetCellStates 168 | { 169 | for (NSIndexPath *path in self.indexPathsToDelete) { 170 | THFacePickerCollectionViewCell *cell = (THFacePickerCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:path]; 171 | [cell highlightSelected:NO]; 172 | } 173 | 174 | [self.indexPathsToDelete removeAllObjects]; 175 | } 176 | 177 | #pragma mark - Private 178 | 179 | - (NSString *)randomString { 180 | const NSInteger alphanumericLettersCount = [kTHAlphanumericCharacters length]; 181 | 182 | NSMutableString *randomString = [[NSMutableString alloc] init]; 183 | for (int i = 0; i < 10; i++) { 184 | [randomString appendFormat: @"%C", [kTHAlphanumericCharacters characterAtIndex: arc4random_uniform(alphanumericLettersCount)]]; 185 | } 186 | 187 | return randomString; 188 | } 189 | 190 | UIImage * uiimageFromOFImage(ofImage inputImage) 191 | { 192 | int width = inputImage.getWidth(); 193 | int height = inputImage.getHeight(); 194 | float pixelForChannel = 3; 195 | CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, inputImage.getPixels(), width*height*pixelForChannel, NULL); 196 | CGImageRef imageRef = CGImageCreate(width, height, 8, 24, pixelForChannel*width, CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault, provider, NULL, NO, kCGRenderingIntentDefault); 197 | UIImage *pngImge = [UIImage imageWithCGImage:imageRef]; 198 | NSData *imageData = UIImagePNGRepresentation(pngImge); 199 | UIImage *output = [UIImage imageWithData:imageData]; 200 | return output; 201 | } 202 | 203 | #pragma mark - UICollectionView Datasource 204 | 205 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 206 | { 207 | return 2; 208 | } 209 | 210 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 211 | { 212 | if ( section == 0 ) { 213 | return mainApp->faces.size(); 214 | } 215 | else { 216 | return self.savedFaces.count; 217 | } 218 | } 219 | 220 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 221 | { 222 | THFacesCollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:kTHSupplementaryHeaderViewReuseIdentifier forIndexPath:indexPath]; 223 | 224 | if ( indexPath.section == 0 ) { 225 | headerView.title = kTHPreInstalledFacesHeaderTitle; 226 | } 227 | else { 228 | headerView.title = kTHSavedFacesHeaderTitle; 229 | } 230 | 231 | return headerView; 232 | } 233 | 234 | -(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 235 | { 236 | THFacePickerCollectionViewCell *cell = (THFacePickerCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kTHCellReuseIdentifier forIndexPath:indexPath]; 237 | 238 | cell.currentIndexPath = indexPath; 239 | [cell clearImage]; 240 | [cell startLoading]; 241 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 242 | 243 | UIImage *faceImage; 244 | if ( indexPath.section == 0 ) { 245 | ofImage preInstalledImage; 246 | preInstalledImage.loadImage(mainApp->faces.getPath(indexPath.row)); 247 | faceImage = uiimageFromOFImage(preInstalledImage); 248 | } 249 | else { 250 | 251 | faceImage = [[UIImage imageWithContentsOfFile:[kTHDocumentsDirectoryPath stringByAppendingPathComponent:[self.savedFaces objectAtIndex:indexPath.row]]] decodedImage]; 252 | } 253 | 254 | dispatch_async(dispatch_get_main_queue(), ^{ 255 | [cell setFaceWithImage:faceImage atIndexPath:indexPath]; 256 | [cell stopLoading]; 257 | }); 258 | }); 259 | 260 | return cell; 261 | } 262 | 263 | @end 264 | -------------------------------------------------------------------------------- /src/Models/Clone.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Clone.cpp 3 | // FaceSubstitutionCamera 4 | // 5 | // Created by James Lilard on 2014/10/20. 6 | // 7 | // 8 | 9 | #include "Clone.h" 10 | 11 | 12 | void Clone::setup(int width, int height) { 13 | ofFbo::Settings settings; 14 | settings.width = width; 15 | settings.height = height; 16 | 17 | buffer.allocate(settings); 18 | srcBlur.allocate(settings); 19 | dstBlur.allocate(settings); 20 | 21 | maskBlurShader.load("shader/maskBlurShader"); 22 | cloneShader.load("shader/cloneShader"); 23 | 24 | strength = 0; 25 | } 26 | 27 | void Clone::maskedBlur(ofTexture& tex, ofTexture& mask, ofFbo& result) { 28 | int k = strength; 29 | 30 | buffer.begin(); 31 | maskBlurShader.begin(); 32 | maskBlurShader.setUniformTexture("tex0", tex, 1); 33 | maskBlurShader.setUniformTexture("mask", mask, 2); 34 | maskBlurShader.setUniform2f("direction", 1./(tex.getWidth()*2), 0.); 35 | maskBlurShader.setUniform1i("k", k); 36 | tex.draw(0, 0); 37 | maskBlurShader.end(); 38 | buffer.end(); 39 | 40 | result.begin(); 41 | maskBlurShader.begin(); 42 | maskBlurShader.setUniformTexture("tex0", buffer, 1); 43 | maskBlurShader.setUniformTexture("mask", mask, 2); 44 | maskBlurShader.setUniform2f("direction", 0., 1./(buffer.getHeight())); 45 | maskBlurShader.setUniform1i("k", k); 46 | buffer.draw(0, 0); 47 | maskBlurShader.end(); 48 | buffer.draw(0, 0); 49 | result.end(); 50 | } 51 | 52 | void Clone::setStrength(int strength) { 53 | this->strength = strength; 54 | } 55 | 56 | void Clone::update(ofTexture& src, ofTexture& dst, ofTexture& mask) { 57 | maskedBlur(src, mask, srcBlur); 58 | maskedBlur(dst, mask, dstBlur); 59 | 60 | buffer.begin(); 61 | ofPushStyle(); 62 | ofEnableAlphaBlending(); 63 | dst.draw(0, 0); 64 | cloneShader.begin(); 65 | cloneShader.setUniformTexture("src", src, 1); 66 | cloneShader.setUniformTexture("srcBlur", srcBlur, 2); 67 | cloneShader.setUniformTexture("dstBlur", dstBlur, 3); 68 | dst.draw(0, 0); 69 | cloneShader.end(); 70 | ofDisableAlphaBlending(); 71 | ofPopStyle(); 72 | buffer.end(); 73 | } 74 | 75 | void Clone::draw(float x, float y) { 76 | buffer.draw(x, y); 77 | } 78 | 79 | 80 | void Clone::debugShader(ofTexture &tex, ofTexture &mask){ 81 | 82 | debugFbo.allocate(buffer.getWidth(), buffer.getHeight()); 83 | debugResultFbo.allocate(buffer.getWidth(), buffer.getHeight()); 84 | ofShader debugShader; 85 | debugShader.load("shader/maskBlurShader"); 86 | 87 | setStrength(16); 88 | 89 | 90 | debugFbo.begin(); 91 | 92 | // debugShader.begin(); 93 | // 94 | // //maskBlurShader.setUniformTexture("tex", tex, 1); 95 | // debugShader.setUniformTexture("tex0", tex, 1); 96 | // debugShader.setUniformTexture("mask", mask, 2); 97 | // debugShader.setUniform2f("direction", 0, 1); 98 | // debugShader.setUniform1i("k", strength); 99 | // mask.draw(0, 0); 100 | // 101 | // debugShader.end(); 102 | maskBlurShader.begin(); 103 | maskBlurShader.setUniformTexture("tex0", tex, 1); 104 | maskBlurShader.setUniformTexture("mask", mask, 2); 105 | //maskBlurShader.setUniform2f("direction", 0., 1./tex.getHeight()); 106 | maskBlurShader.setUniform2f("direction", 1./tex.getWidth(), 0.); 107 | maskBlurShader.setUniform1i("k", strength); 108 | tex.draw(0, 0); 109 | maskBlurShader.end(); 110 | 111 | 112 | debugFbo.end(); 113 | 114 | // debugResultFbo.begin(); 115 | // 116 | // maskBlurShader.setUniformTexture("tex0", debugFbo, 1); 117 | // maskBlurShader.setUniformTexture("mask", mask, 2); 118 | // maskBlurShader.setUniform2f("direction", 0., 1./tex.getHeight()); 119 | // maskBlurShader.setUniform1i("k", strength); 120 | // debugFbo.draw(0, 0); 121 | // 122 | // debugResultFbo.end(); 123 | 124 | 125 | } -------------------------------------------------------------------------------- /src/Models/Clone.h: -------------------------------------------------------------------------------- 1 | // 2 | // Clone.h 3 | // FaceSubstitutionCamera 4 | // 5 | // Created by James Lilard on 2014/10/20. 6 | // 7 | // 8 | 9 | #ifndef __FaceSubstitutionCamera__Clone__ 10 | #define __FaceSubstitutionCamera__Clone__ 11 | 12 | #include "ofMain.h" 13 | 14 | class Clone{ 15 | 16 | public: 17 | void setup(int width, int height); 18 | void setStrength(int strength); 19 | void update(ofTexture &src, ofTexture &dst, ofTexture &mask); 20 | void draw(float x, float y); 21 | 22 | 23 | //protected: 24 | void maskedBlur(ofTexture &tex, ofTexture &mask, ofFbo &result); 25 | ofFbo buffer, srcBlur, dstBlur; 26 | ofShader maskBlurShader, cloneShader; 27 | int strength; 28 | 29 | 30 | ofFbo debugFbo,debugResultFbo; 31 | void debugShader(ofTexture &tex, ofTexture &mask); 32 | 33 | }; 34 | 35 | #endif /* defined(__FaceSubstitutionCamera__Clone__) */ 36 | -------------------------------------------------------------------------------- /src/Resources/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /src/Resources/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/Default.png -------------------------------------------------------------------------------- /src/Resources/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/Default@2x.png -------------------------------------------------------------------------------- /src/Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/Icon.png -------------------------------------------------------------------------------- /src/Resources/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/Icon@2x.png -------------------------------------------------------------------------------- /src/Resources/MediaAssets.xcassets/camera.imageset/Camera-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/MediaAssets.xcassets/camera.imageset/Camera-100.png -------------------------------------------------------------------------------- /src/Resources/MediaAssets.xcassets/camera.imageset/Camera-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/MediaAssets.xcassets/camera.imageset/Camera-64.png -------------------------------------------------------------------------------- /src/Resources/MediaAssets.xcassets/camera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Camera-64.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "Camera-100.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /src/Resources/MediaAssets.xcassets/close.imageset/Close-100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/MediaAssets.xcassets/close.imageset/Close-100.png -------------------------------------------------------------------------------- /src/Resources/MediaAssets.xcassets/close.imageset/Close-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackerati/THVideoFaceSwapper/118c3a8cad00b000aa1863238de28b0ec3e96dd8/src/Resources/MediaAssets.xcassets/close.imageset/Close-64.png -------------------------------------------------------------------------------- /src/Resources/MediaAssets.xcassets/close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x", 10 | "filename" : "Close-64.png" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x", 15 | "filename" : "Close-100.png" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /src/View Controllers/THPhotoPickerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // THPhotoPickerViewController.h 3 | // videoApp 4 | // 5 | // Created by Clayton Rieck on 4/8/15. 6 | // 7 | // 8 | 9 | #include 10 | 11 | @interface THPhotoPickerViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /src/View Controllers/THPhotoPickerViewController.mm: -------------------------------------------------------------------------------- 1 | // 2 | // FSPhotoPickerViewController.m 3 | // videoApp 4 | // 5 | // Created by Clayton Rieck on 4/8/15. 6 | // 7 | // 8 | 9 | #import "THPhotoPickerViewController.h" 10 | #include "ofApp.h" 11 | 12 | #import "THFacePickerCollectionViewCell.h" 13 | #import "THFacesCollectionViewDataSource.h" 14 | #import "MBProgressHUD.h" 15 | 16 | static NSString * const kTHFacePickerViewControllerTitle = @"Face Selector"; 17 | static NSString * const kTHDeleteButtonSingleItemTitle = @"Delete Item"; 18 | static NSString * const kTHDeleteButtonMultiItemTitle = @"Delete Items"; 19 | 20 | static NSArray * const kTHLoadingDetails = @[@"You're gonna look great!", @"Ooo how handsome", @"Your alias is on the way!", @"Better than Mrs. Doubtfire"]; 21 | 22 | static const CGSize kTHCellSize = (CGSize){100.0f, 100.0f}; 23 | static const CGFloat kTHItemSpacing = 2.0f; 24 | static const UIEdgeInsets kTHCollectionViewEdgeInsets = (UIEdgeInsets){0.0f, 8.0f, 8.0f, 8.0f}; 25 | 26 | @interface THPhotoPickerViewController () 27 | 32 | { 33 | ofApp *mainApp; 34 | } 35 | 36 | @property (nonatomic) UICollectionView *facesCollectionView; 37 | @property (nonatomic) THFacesCollectionViewDataSource *dataSource; 38 | @property (nonatomic) UIButton *deleteButton; 39 | @property (nonatomic) UIImage *takenPhoto; 40 | 41 | @end 42 | 43 | @implementation THPhotoPickerViewController 44 | 45 | - (instancetype)init 46 | { 47 | self = [super init]; 48 | if ( self ) { 49 | mainApp = (ofApp *)ofGetAppPtr(); 50 | self.title = kTHFacePickerViewControllerTitle; 51 | } 52 | return self; 53 | } 54 | 55 | - (void)setupMenuButtons 56 | { 57 | UIBarButtonItem *dismissButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"close"] style:UIBarButtonItemStylePlain target:self action:@selector(dismissVC)]; 58 | self.navigationItem.leftBarButtonItem = dismissButton; 59 | 60 | UIBarButtonItem *cameraButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"camera"] style:UIBarButtonItemStylePlain target:self action:@selector(presentCameraPicker)]; 61 | self.navigationItem.rightBarButtonItem = cameraButton; 62 | } 63 | 64 | - (void)setupDeleteButton 65 | { 66 | const CGFloat navBarHeight = self.navigationController.navigationBar.frame.size.height; 67 | 68 | _deleteButton = [[UIButton alloc] initWithFrame:self.navigationController.navigationBar.frame]; 69 | [_deleteButton addTarget:self action:@selector(deleteSelectedItems) forControlEvents:UIControlEventTouchUpInside]; 70 | _deleteButton.backgroundColor = [UIColor colorWithRed:0.9f green:0.3f blue:0.26f alpha:1.0f]; 71 | _deleteButton.transform = CGAffineTransformMakeTranslation(0.0f, -navBarHeight); 72 | _deleteButton.hidden = YES; 73 | _deleteButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 74 | [self.navigationController.navigationBar addSubview:_deleteButton]; 75 | } 76 | 77 | - (void)setupCollectionView 78 | { 79 | const CGRect collectionViewFrame = CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.view.frame.size.height); 80 | const CGSize headerViewSize = CGSizeMake(self.view.frame.size.width, 30.0f); 81 | 82 | UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; 83 | flowLayout.headerReferenceSize = headerViewSize; 84 | flowLayout.itemSize = kTHCellSize; 85 | flowLayout.minimumInteritemSpacing = kTHItemSpacing; 86 | flowLayout.minimumLineSpacing = kTHItemSpacing; 87 | 88 | _facesCollectionView = [[UICollectionView alloc] initWithFrame:collectionViewFrame collectionViewLayout:flowLayout]; 89 | _facesCollectionView.backgroundColor = [UIColor whiteColor]; 90 | _facesCollectionView.contentInset = kTHCollectionViewEdgeInsets; 91 | _facesCollectionView.delegate = self; 92 | _dataSource = [[THFacesCollectionViewDataSource alloc] initWithCollectionView:_facesCollectionView]; 93 | _facesCollectionView.dataSource = _dataSource; 94 | _facesCollectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 95 | [self.view addSubview:_facesCollectionView]; 96 | 97 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)]; 98 | longPress.minimumPressDuration = 0.5f; 99 | longPress.numberOfTouchesRequired = 1; 100 | longPress.delaysTouchesBegan = YES; // prevent didHighlightItemAtIndexPath from being called first 101 | [_facesCollectionView addGestureRecognizer:longPress]; 102 | } 103 | 104 | - (void)dealloc 105 | { 106 | _facesCollectionView = nil; 107 | mainApp = NULL; 108 | } 109 | 110 | - (void)loadView 111 | { 112 | self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 113 | self.view.backgroundColor = [UIColor whiteColor]; 114 | 115 | [self setupMenuButtons]; 116 | [self setupCollectionView]; 117 | } 118 | 119 | - (void)viewDidAppear:(BOOL)animated 120 | { 121 | [super viewDidAppear:animated]; 122 | [self setupDeleteButton]; // prevents bug where button doesn't appear after taking a photo and trying to delete a saved photo 123 | } 124 | 125 | #pragma mark - Private 126 | 127 | - (void)dismissVC 128 | { 129 | [self.dataSource resetCellStates]; 130 | 131 | mainApp->setupCam([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.width); 132 | [self.navigationController dismissViewControllerAnimated:YES completion:^{ 133 | [MBProgressHUD hideHUDForView:self.view animated:YES]; 134 | }]; 135 | } 136 | 137 | - (void)presentCameraPicker 138 | { 139 | UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init]; 140 | imagePicker.delegate = self; 141 | imagePicker.allowsEditing = NO; 142 | imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera; 143 | 144 | [self.navigationController presentViewController:imagePicker animated:YES completion:nil]; 145 | } 146 | 147 | - (NSString *)randomLoadingDetail 148 | { 149 | const NSInteger lowerBound = 0; 150 | const NSInteger upperBound = kTHLoadingDetails.count; 151 | const NSInteger randomPosition = lowerBound + arc4random() % (upperBound - lowerBound); 152 | return kTHLoadingDetails[randomPosition]; 153 | } 154 | 155 | - (void)showLoadingHUD 156 | { 157 | MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES]; 158 | hud.mode = MBProgressHUDModeIndeterminate; 159 | hud.labelText = @"Loading Face"; 160 | hud.detailsLabelText = [self randomLoadingDetail]; 161 | } 162 | 163 | - (void)showDeleteButton:(BOOL)show 164 | { 165 | CGAffineTransform targetTransform; 166 | if ( show ) { 167 | [self.deleteButton setHidden:!show]; 168 | targetTransform = CGAffineTransformMakeTranslation(0.0f, 0.0f); 169 | } 170 | else { 171 | targetTransform = CGAffineTransformMakeTranslation(0.0f, -self.navigationController.navigationBar.frame.size.height); 172 | } 173 | 174 | [UIView animateWithDuration:0.1f 175 | animations:^{ 176 | self.deleteButton.transform = targetTransform; 177 | } 178 | completion:^(BOOL finished){ 179 | [self.deleteButton setHidden:!show]; 180 | }]; 181 | } 182 | 183 | #pragma mark - UIAlertView Delegate 184 | 185 | - (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex 186 | { 187 | BOOL save = NO; 188 | if ( buttonIndex == 1 ) { 189 | save = YES; 190 | } 191 | 192 | [self.navigationController dismissViewControllerAnimated:YES completion:^{ 193 | [self showLoadingHUD]; 194 | [self.dataSource loadFace:self.takenPhoto saveImage:save withCompletion:^{ 195 | self.takenPhoto = nil; 196 | [self dismissVC]; 197 | }]; 198 | }]; 199 | } 200 | 201 | #pragma mark - UIImagePickerController Delegate 202 | 203 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 204 | { 205 | self.takenPhoto = (UIImage *)[info[UIImagePickerControllerOriginalImage] copy]; // need to copy or else you'll try to load the reference to the image which will be deallocated outside of this scope 206 | 207 | mainApp->cam.close(); 208 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Save Photo For Later?" 209 | message:@"Saving will allow you to use this face later on" 210 | delegate:self 211 | cancelButtonTitle:@"No Thanks" 212 | otherButtonTitles:@"Right On!", nil]; 213 | [alertView show]; 214 | } 215 | 216 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker 217 | { 218 | [picker dismissViewControllerAnimated:YES completion:nil]; 219 | } 220 | 221 | #pragma mark - UICollectionView Delegate 222 | 223 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 224 | { 225 | [self showLoadingHUD]; 226 | 227 | dispatch_async(dispatch_queue_create("imageLoadingQueue", NULL), ^{ 228 | dispatch_async(dispatch_get_main_queue(), ^{ 229 | if ( indexPath.section == 0 ) { 230 | mainApp->loadFace(mainApp->faces.getPath(indexPath.row)); 231 | } 232 | else { 233 | NSString *imageName = [self.dataSource savedImageNameAtIndexPath:indexPath]; 234 | ofImage savedImage; 235 | ofxiOSUIImageToOFImage([self.dataSource imageFromDocumentsDirectoryNamed:imageName], savedImage); 236 | mainApp->loadOFImage(savedImage); 237 | } 238 | 239 | [self dismissVC]; 240 | }); 241 | }); 242 | } 243 | 244 | #pragma mark - UIGestureRecognizer Selectors 245 | 246 | - (void)longPress:(UIGestureRecognizer *)gesture 247 | { 248 | switch ( gesture.state ) { 249 | case UIGestureRecognizerStateBegan: { 250 | const CGPoint gesturePoint = [gesture locationInView:self.facesCollectionView]; 251 | NSIndexPath *pointIndexPath = [self.facesCollectionView indexPathForItemAtPoint:gesturePoint]; 252 | 253 | if ( pointIndexPath ) { 254 | 255 | if ( pointIndexPath.section == 1 ) { // should only be able to delete saved images taken via camera 256 | 257 | THFacePickerCollectionViewCell *selectedCell = (THFacePickerCollectionViewCell *)[self.facesCollectionView cellForItemAtIndexPath:pointIndexPath]; 258 | [selectedCell highlightSelected:!selectedCell.highlightSelected]; 259 | 260 | if ( selectedCell.highlightSelected ) { 261 | [self.dataSource addIndexPathToDelete:pointIndexPath]; 262 | } 263 | else { 264 | [self.dataSource removeIndexPathToDelete:pointIndexPath]; 265 | } 266 | 267 | const NSInteger itemsToDelete = [self.dataSource numberOfItemsToDelete]; 268 | if ( itemsToDelete > 0 ) { 269 | 270 | if ( itemsToDelete > 1 ) { 271 | [self.deleteButton setTitle:kTHDeleteButtonMultiItemTitle forState:UIControlStateNormal]; 272 | } 273 | else { 274 | [self.deleteButton setTitle:kTHDeleteButtonSingleItemTitle forState:UIControlStateNormal]; 275 | } 276 | 277 | if ( self.deleteButton.isHidden ) { 278 | [self showDeleteButton:YES]; 279 | } 280 | } 281 | else { 282 | if ( !self.deleteButton.isHidden ) { 283 | [self showDeleteButton:NO]; 284 | } 285 | } 286 | } 287 | } 288 | break; 289 | } 290 | case UIGestureRecognizerStateEnded: 291 | case UIGestureRecognizerStateFailed: 292 | case UIGestureRecognizerStateCancelled: 293 | case UIGestureRecognizerStateChanged: 294 | default: 295 | break; 296 | } 297 | } 298 | 299 | #pragma mark - UIButton Selectors 300 | 301 | - (void)deleteSelectedItems 302 | { 303 | [self.dataSource deleteSelectedItems:^{ 304 | [self showDeleteButton:NO]; 305 | }]; 306 | } 307 | 308 | @end 309 | -------------------------------------------------------------------------------- /src/Views/Loading/MBProgressHUD.h: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.h 3 | // Version 0.9.1 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | // This code is distributed under the terms and conditions of the MIT license. 8 | 9 | // Copyright (c) 2009-2015 Matej Bukovinski 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | // THE SOFTWARE. 28 | 29 | #import 30 | #import 31 | #import 32 | 33 | @protocol MBProgressHUDDelegate; 34 | 35 | 36 | typedef NS_ENUM(NSInteger, MBProgressHUDMode) { 37 | /** Progress is shown using an UIActivityIndicatorView. This is the default. */ 38 | MBProgressHUDModeIndeterminate, 39 | /** Progress is shown using a round, pie-chart like, progress view. */ 40 | MBProgressHUDModeDeterminate, 41 | /** Progress is shown using a horizontal progress bar */ 42 | MBProgressHUDModeDeterminateHorizontalBar, 43 | /** Progress is shown using a ring-shaped progress view. */ 44 | MBProgressHUDModeAnnularDeterminate, 45 | /** Shows a custom view */ 46 | MBProgressHUDModeCustomView, 47 | /** Shows only labels */ 48 | MBProgressHUDModeText 49 | }; 50 | 51 | typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) { 52 | /** Opacity animation */ 53 | MBProgressHUDAnimationFade, 54 | /** Opacity + scale animation */ 55 | MBProgressHUDAnimationZoom, 56 | MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom, 57 | MBProgressHUDAnimationZoomIn 58 | }; 59 | 60 | 61 | #ifndef MB_INSTANCETYPE 62 | #if __has_feature(objc_instancetype) 63 | #define MB_INSTANCETYPE instancetype 64 | #else 65 | #define MB_INSTANCETYPE id 66 | #endif 67 | #endif 68 | 69 | #ifndef MB_STRONG 70 | #if __has_feature(objc_arc) 71 | #define MB_STRONG strong 72 | #else 73 | #define MB_STRONG retain 74 | #endif 75 | #endif 76 | 77 | #ifndef MB_WEAK 78 | #if __has_feature(objc_arc_weak) 79 | #define MB_WEAK weak 80 | #elif __has_feature(objc_arc) 81 | #define MB_WEAK unsafe_unretained 82 | #else 83 | #define MB_WEAK assign 84 | #endif 85 | #endif 86 | 87 | #if NS_BLOCKS_AVAILABLE 88 | typedef void (^MBProgressHUDCompletionBlock)(); 89 | #endif 90 | 91 | 92 | /** 93 | * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. 94 | * 95 | * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. 96 | * The MBProgressHUD window spans over the entire space given to it by the initWithFrame constructor and catches all 97 | * user input on this region, thereby preventing the user operations on components below the view. The HUD itself is 98 | * drawn centered as a rounded semi-transparent view which resizes depending on the user specified content. 99 | * 100 | * This view supports four modes of operation: 101 | * - MBProgressHUDModeIndeterminate - shows a UIActivityIndicatorView 102 | * - MBProgressHUDModeDeterminate - shows a custom round progress indicator 103 | * - MBProgressHUDModeAnnularDeterminate - shows a custom annular progress indicator 104 | * - MBProgressHUDModeCustomView - shows an arbitrary, user specified view (@see customView) 105 | * 106 | * All three modes can have optional labels assigned: 107 | * - If the labelText property is set and non-empty then a label containing the provided content is placed below the 108 | * indicator view. 109 | * - If also the detailsLabelText property is set then another label is placed below the first label. 110 | */ 111 | @interface MBProgressHUD : UIView 112 | 113 | /** 114 | * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. 115 | * 116 | * @param view The view that the HUD will be added to 117 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 118 | * animations while appearing. 119 | * @return A reference to the created HUD. 120 | * 121 | * @see hideHUDForView:animated: 122 | * @see animationType 123 | */ 124 | + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; 125 | 126 | /** 127 | * Finds the top-most HUD subview and hides it. The counterpart to this method is showHUDAddedTo:animated:. 128 | * 129 | * @param view The view that is going to be searched for a HUD subview. 130 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 131 | * animations while disappearing. 132 | * @return YES if a HUD was found and removed, NO otherwise. 133 | * 134 | * @see showHUDAddedTo:animated: 135 | * @see animationType 136 | */ 137 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; 138 | 139 | /** 140 | * Finds all the HUD subviews and hides them. 141 | * 142 | * @param view The view that is going to be searched for HUD subviews. 143 | * @param animated If set to YES the HUDs will disappear using the current animationType. If set to NO the HUDs will not use 144 | * animations while disappearing. 145 | * @return the number of HUDs found and removed. 146 | * 147 | * @see hideHUDForView:animated: 148 | * @see animationType 149 | */ 150 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated; 151 | 152 | /** 153 | * Finds the top-most HUD subview and returns it. 154 | * 155 | * @param view The view that is going to be searched. 156 | * @return A reference to the last HUD subview discovered. 157 | */ 158 | + (MB_INSTANCETYPE)HUDForView:(UIView *)view; 159 | 160 | /** 161 | * Finds all HUD subviews and returns them. 162 | * 163 | * @param view The view that is going to be searched. 164 | * @return All found HUD views (array of MBProgressHUD objects). 165 | */ 166 | + (NSArray *)allHUDsForView:(UIView *)view; 167 | 168 | /** 169 | * A convenience constructor that initializes the HUD with the window's bounds. Calls the designated constructor with 170 | * window.bounds as the parameter. 171 | * 172 | * @param window The window instance that will provide the bounds for the HUD. Should be the same instance as 173 | * the HUD's superview (i.e., the window that the HUD will be added to). 174 | */ 175 | - (id)initWithWindow:(UIWindow *)window; 176 | 177 | /** 178 | * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with 179 | * view.bounds as the parameter 180 | * 181 | * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as 182 | * the HUD's superview (i.e., the view that the HUD will be added to). 183 | */ 184 | - (id)initWithView:(UIView *)view; 185 | 186 | /** 187 | * Display the HUD. You need to make sure that the main thread completes its run loop soon after this method call so 188 | * the user interface can be updated. Call this method when your task is already set-up to be executed in a new thread 189 | * (e.g., when using something like NSOperation or calling an asynchronous call like NSURLRequest). 190 | * 191 | * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use 192 | * animations while appearing. 193 | * 194 | * @see animationType 195 | */ 196 | - (void)show:(BOOL)animated; 197 | 198 | /** 199 | * Hide the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 200 | * hide the HUD when your task completes. 201 | * 202 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 203 | * animations while disappearing. 204 | * 205 | * @see animationType 206 | */ 207 | - (void)hide:(BOOL)animated; 208 | 209 | /** 210 | * Hide the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to 211 | * hide the HUD when your task completes. 212 | * 213 | * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use 214 | * animations while disappearing. 215 | * @param delay Delay in seconds until the HUD is hidden. 216 | * 217 | * @see animationType 218 | */ 219 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay; 220 | 221 | /** 222 | * Shows the HUD while a background task is executing in a new thread, then hides the HUD. 223 | * 224 | * This method also takes care of autorelease pools so your method does not have to be concerned with setting up a 225 | * pool. 226 | * 227 | * @param method The method to be executed while the HUD is shown. This method will be executed in a new thread. 228 | * @param target The object that the target method belongs to. 229 | * @param object An optional object to be passed to the method. 230 | * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will not use 231 | * animations while (dis)appearing. 232 | */ 233 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated; 234 | 235 | #if NS_BLOCKS_AVAILABLE 236 | 237 | /** 238 | * Shows the HUD while a block is executing on a background queue, then hides the HUD. 239 | * 240 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 241 | */ 242 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block; 243 | 244 | /** 245 | * Shows the HUD while a block is executing on a background queue, then hides the HUD. 246 | * 247 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 248 | */ 249 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(MBProgressHUDCompletionBlock)completion; 250 | 251 | /** 252 | * Shows the HUD while a block is executing on the specified dispatch queue, then hides the HUD. 253 | * 254 | * @see showAnimated:whileExecutingBlock:onQueue:completionBlock: 255 | */ 256 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue; 257 | 258 | /** 259 | * Shows the HUD while a block is executing on the specified dispatch queue, executes completion block on the main queue, and then hides the HUD. 260 | * 261 | * @param animated If set to YES the HUD will (dis)appear using the current animationType. If set to NO the HUD will 262 | * not use animations while (dis)appearing. 263 | * @param block The block to be executed while the HUD is shown. 264 | * @param queue The dispatch queue on which the block should be executed. 265 | * @param completion The block to be executed on completion. 266 | * 267 | * @see completionBlock 268 | */ 269 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 270 | completionBlock:(MBProgressHUDCompletionBlock)completion; 271 | 272 | /** 273 | * A block that gets called after the HUD was completely hidden. 274 | */ 275 | @property (copy) MBProgressHUDCompletionBlock completionBlock; 276 | 277 | #endif 278 | 279 | /** 280 | * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. 281 | * 282 | * @see MBProgressHUDMode 283 | */ 284 | @property (assign) MBProgressHUDMode mode; 285 | 286 | /** 287 | * The animation type that should be used when the HUD is shown and hidden. 288 | * 289 | * @see MBProgressHUDAnimation 290 | */ 291 | @property (assign) MBProgressHUDAnimation animationType; 292 | 293 | /** 294 | * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. 295 | * For best results use a 37 by 37 pixel view (so the bounds match the built in indicator bounds). 296 | */ 297 | @property (MB_STRONG) UIView *customView; 298 | 299 | /** 300 | * The HUD delegate object. 301 | * 302 | * @see MBProgressHUDDelegate 303 | */ 304 | @property (MB_WEAK) id delegate; 305 | 306 | /** 307 | * An optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit 308 | * the entire text. If the text is too long it will get clipped by displaying "..." at the end. If left unchanged or 309 | * set to @"", then no message is displayed. 310 | */ 311 | @property (copy) NSString *labelText; 312 | 313 | /** 314 | * An optional details message displayed below the labelText message. This message is displayed only if the labelText 315 | * property is also set and is different from an empty string (@""). The details text can span multiple lines. 316 | */ 317 | @property (copy) NSString *detailsLabelText; 318 | 319 | /** 320 | * The opacity of the HUD window. Defaults to 0.8 (80% opacity). 321 | */ 322 | @property (assign) float opacity; 323 | 324 | /** 325 | * The color of the HUD window. Defaults to black. If this property is set, color is set using 326 | * this UIColor and the opacity property is not used. using retain because performing copy on 327 | * UIColor base colors (like [UIColor greenColor]) cause problems with the copyZone. 328 | */ 329 | @property (MB_STRONG) UIColor *color; 330 | 331 | /** 332 | * The x-axis offset of the HUD relative to the centre of the superview. 333 | */ 334 | @property (assign) float xOffset; 335 | 336 | /** 337 | * The y-axis offset of the HUD relative to the centre of the superview. 338 | */ 339 | @property (assign) float yOffset; 340 | 341 | /** 342 | * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). 343 | * Defaults to 20.0 344 | */ 345 | @property (assign) float margin; 346 | 347 | /** 348 | * The corner radius for the HUD 349 | * Defaults to 10.0 350 | */ 351 | @property (assign) float cornerRadius; 352 | 353 | /** 354 | * Cover the HUD background view with a radial gradient. 355 | */ 356 | @property (assign) BOOL dimBackground; 357 | 358 | /* 359 | * Grace period is the time (in seconds) that the invoked method may be run without 360 | * showing the HUD. If the task finishes before the grace time runs out, the HUD will 361 | * not be shown at all. 362 | * This may be used to prevent HUD display for very short tasks. 363 | * Defaults to 0 (no grace time). 364 | * Grace time functionality is only supported when the task status is known! 365 | * @see taskInProgress 366 | */ 367 | @property (assign) float graceTime; 368 | 369 | /** 370 | * The minimum time (in seconds) that the HUD is shown. 371 | * This avoids the problem of the HUD being shown and than instantly hidden. 372 | * Defaults to 0 (no minimum show time). 373 | */ 374 | @property (assign) float minShowTime; 375 | 376 | /** 377 | * Indicates that the executed operation is in progress. Needed for correct graceTime operation. 378 | * If you don't set a graceTime (different than 0.0) this does nothing. 379 | * This property is automatically set when using showWhileExecuting:onTarget:withObject:animated:. 380 | * When threading is done outside of the HUD (i.e., when the show: and hide: methods are used directly), 381 | * you need to set this property when your task starts and completes in order to have normal graceTime 382 | * functionality. 383 | */ 384 | @property (assign) BOOL taskInProgress; 385 | 386 | /** 387 | * Removes the HUD from its parent view when hidden. 388 | * Defaults to NO. 389 | */ 390 | @property (assign) BOOL removeFromSuperViewOnHide; 391 | 392 | /** 393 | * Font to be used for the main label. Set this property if the default is not adequate. 394 | */ 395 | @property (MB_STRONG) UIFont* labelFont; 396 | 397 | /** 398 | * Color to be used for the main label. Set this property if the default is not adequate. 399 | */ 400 | @property (MB_STRONG) UIColor* labelColor; 401 | 402 | /** 403 | * Font to be used for the details label. Set this property if the default is not adequate. 404 | */ 405 | @property (MB_STRONG) UIFont* detailsLabelFont; 406 | 407 | /** 408 | * Color to be used for the details label. Set this property if the default is not adequate. 409 | */ 410 | @property (MB_STRONG) UIColor* detailsLabelColor; 411 | 412 | /** 413 | * The color of the activity indicator. Defaults to [UIColor whiteColor] 414 | * Does nothing on pre iOS 5. 415 | */ 416 | @property (MB_STRONG) UIColor *activityIndicatorColor; 417 | 418 | /** 419 | * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. 420 | */ 421 | @property (assign) float progress; 422 | 423 | /** 424 | * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). 425 | */ 426 | @property (assign) CGSize minSize; 427 | 428 | 429 | /** 430 | * The actual size of the HUD bezel. 431 | * You can use this to limit touch handling on the bezel aria only. 432 | * @see https://github.com/jdg/MBProgressHUD/pull/200 433 | */ 434 | @property (atomic, assign, readonly) CGSize size; 435 | 436 | 437 | /** 438 | * Force the HUD dimensions to be equal if possible. 439 | */ 440 | @property (assign, getter = isSquare) BOOL square; 441 | 442 | @end 443 | 444 | 445 | @protocol MBProgressHUDDelegate 446 | 447 | @optional 448 | 449 | /** 450 | * Called after the HUD was fully hidden from the screen. 451 | */ 452 | - (void)hudWasHidden:(MBProgressHUD *)hud; 453 | 454 | @end 455 | 456 | 457 | /** 458 | * A progress view for showing definite progress by filling up a circle (pie chart). 459 | */ 460 | @interface MBRoundProgressView : UIView 461 | 462 | /** 463 | * Progress (0.0 to 1.0) 464 | */ 465 | @property (nonatomic, assign) float progress; 466 | 467 | /** 468 | * Indicator progress color. 469 | * Defaults to white [UIColor whiteColor] 470 | */ 471 | @property (nonatomic, MB_STRONG) UIColor *progressTintColor; 472 | 473 | /** 474 | * Indicator background (non-progress) color. 475 | * Defaults to translucent white (alpha 0.1) 476 | */ 477 | @property (nonatomic, MB_STRONG) UIColor *backgroundTintColor; 478 | 479 | /* 480 | * Display mode - NO = round or YES = annular. Defaults to round. 481 | */ 482 | @property (nonatomic, assign, getter = isAnnular) BOOL annular; 483 | 484 | @end 485 | 486 | 487 | /** 488 | * A flat bar progress view. 489 | */ 490 | @interface MBBarProgressView : UIView 491 | 492 | /** 493 | * Progress (0.0 to 1.0) 494 | */ 495 | @property (nonatomic, assign) float progress; 496 | 497 | /** 498 | * Bar border line color. 499 | * Defaults to white [UIColor whiteColor]. 500 | */ 501 | @property (nonatomic, MB_STRONG) UIColor *lineColor; 502 | 503 | /** 504 | * Bar background color. 505 | * Defaults to clear [UIColor clearColor]; 506 | */ 507 | @property (nonatomic, MB_STRONG) UIColor *progressRemainingColor; 508 | 509 | /** 510 | * Bar progress color. 511 | * Defaults to white [UIColor whiteColor]. 512 | */ 513 | @property (nonatomic, MB_STRONG) UIColor *progressColor; 514 | 515 | @end 516 | -------------------------------------------------------------------------------- /src/Views/Loading/MBProgressHUD.m: -------------------------------------------------------------------------------- 1 | // 2 | // MBProgressHUD.m 3 | // Version 0.9.1 4 | // Created by Matej Bukovinski on 2.4.09. 5 | // 6 | 7 | #import "MBProgressHUD.h" 8 | #import 9 | 10 | 11 | #if __has_feature(objc_arc) 12 | #define MB_AUTORELEASE(exp) exp 13 | #define MB_RELEASE(exp) exp 14 | #define MB_RETAIN(exp) exp 15 | #else 16 | #define MB_AUTORELEASE(exp) [exp autorelease] 17 | #define MB_RELEASE(exp) [exp release] 18 | #define MB_RETAIN(exp) [exp retain] 19 | #endif 20 | 21 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 22 | #define MBLabelAlignmentCenter NSTextAlignmentCenter 23 | #else 24 | #define MBLabelAlignmentCenter UITextAlignmentCenter 25 | #endif 26 | 27 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 28 | #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text \ 29 | sizeWithAttributes:@{NSFontAttributeName:font}] : CGSizeZero; 30 | #else 31 | #define MB_TEXTSIZE(text, font) [text length] > 0 ? [text sizeWithFont:font] : CGSizeZero; 32 | #endif 33 | 34 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 35 | #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \ 36 | boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin) \ 37 | attributes:@{NSFontAttributeName:font} context:nil].size : CGSizeZero; 38 | #else 39 | #define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \ 40 | sizeWithFont:font constrainedToSize:maxSize lineBreakMode:mode] : CGSizeZero; 41 | #endif 42 | 43 | #ifndef kCFCoreFoundationVersionNumber_iOS_7_0 44 | #define kCFCoreFoundationVersionNumber_iOS_7_0 847.20 45 | #endif 46 | 47 | #ifndef kCFCoreFoundationVersionNumber_iOS_8_0 48 | #define kCFCoreFoundationVersionNumber_iOS_8_0 1129.15 49 | #endif 50 | 51 | 52 | static const CGFloat kPadding = 4.f; 53 | static const CGFloat kLabelFontSize = 16.f; 54 | static const CGFloat kDetailsLabelFontSize = 12.f; 55 | 56 | 57 | @interface MBProgressHUD () { 58 | BOOL useAnimation; 59 | SEL methodForExecution; 60 | id targetForExecution; 61 | id objectForExecution; 62 | UILabel *label; 63 | UILabel *detailsLabel; 64 | BOOL isFinished; 65 | CGAffineTransform rotationTransform; 66 | } 67 | 68 | @property (atomic, MB_STRONG) UIView *indicator; 69 | @property (atomic, MB_STRONG) NSTimer *graceTimer; 70 | @property (atomic, MB_STRONG) NSTimer *minShowTimer; 71 | @property (atomic, MB_STRONG) NSDate *showStarted; 72 | 73 | @end 74 | 75 | 76 | @implementation MBProgressHUD 77 | 78 | #pragma mark - Properties 79 | 80 | @synthesize animationType; 81 | @synthesize delegate; 82 | @synthesize opacity; 83 | @synthesize color; 84 | @synthesize labelFont; 85 | @synthesize labelColor; 86 | @synthesize detailsLabelFont; 87 | @synthesize detailsLabelColor; 88 | @synthesize indicator; 89 | @synthesize xOffset; 90 | @synthesize yOffset; 91 | @synthesize minSize; 92 | @synthesize square; 93 | @synthesize margin; 94 | @synthesize dimBackground; 95 | @synthesize graceTime; 96 | @synthesize minShowTime; 97 | @synthesize graceTimer; 98 | @synthesize minShowTimer; 99 | @synthesize taskInProgress; 100 | @synthesize removeFromSuperViewOnHide; 101 | @synthesize customView; 102 | @synthesize showStarted; 103 | @synthesize mode; 104 | @synthesize labelText; 105 | @synthesize detailsLabelText; 106 | @synthesize progress; 107 | @synthesize size; 108 | @synthesize activityIndicatorColor; 109 | #if NS_BLOCKS_AVAILABLE 110 | @synthesize completionBlock; 111 | #endif 112 | 113 | #pragma mark - Class methods 114 | 115 | + (MB_INSTANCETYPE)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { 116 | MBProgressHUD *hud = [[self alloc] initWithView:view]; 117 | hud.removeFromSuperViewOnHide = YES; 118 | [view addSubview:hud]; 119 | [hud show:animated]; 120 | return MB_AUTORELEASE(hud); 121 | } 122 | 123 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { 124 | MBProgressHUD *hud = [self HUDForView:view]; 125 | if (hud != nil) { 126 | hud.removeFromSuperViewOnHide = YES; 127 | [hud hide:animated]; 128 | return YES; 129 | } 130 | return NO; 131 | } 132 | 133 | + (NSUInteger)hideAllHUDsForView:(UIView *)view animated:(BOOL)animated { 134 | NSArray *huds = [MBProgressHUD allHUDsForView:view]; 135 | for (MBProgressHUD *hud in huds) { 136 | hud.removeFromSuperViewOnHide = YES; 137 | [hud hide:animated]; 138 | } 139 | return [huds count]; 140 | } 141 | 142 | + (MB_INSTANCETYPE)HUDForView:(UIView *)view { 143 | NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; 144 | for (UIView *subview in subviewsEnum) { 145 | if ([subview isKindOfClass:self]) { 146 | return (MBProgressHUD *)subview; 147 | } 148 | } 149 | return nil; 150 | } 151 | 152 | + (NSArray *)allHUDsForView:(UIView *)view { 153 | NSMutableArray *huds = [NSMutableArray array]; 154 | NSArray *subviews = view.subviews; 155 | for (UIView *aView in subviews) { 156 | if ([aView isKindOfClass:self]) { 157 | [huds addObject:aView]; 158 | } 159 | } 160 | return [NSArray arrayWithArray:huds]; 161 | } 162 | 163 | #pragma mark - Lifecycle 164 | 165 | - (id)initWithFrame:(CGRect)frame { 166 | self = [super initWithFrame:frame]; 167 | if (self) { 168 | // Set default values for properties 169 | self.animationType = MBProgressHUDAnimationFade; 170 | self.mode = MBProgressHUDModeIndeterminate; 171 | self.labelText = nil; 172 | self.detailsLabelText = nil; 173 | self.opacity = 0.8f; 174 | self.color = nil; 175 | self.labelFont = [UIFont boldSystemFontOfSize:kLabelFontSize]; 176 | self.labelColor = [UIColor whiteColor]; 177 | self.detailsLabelFont = [UIFont boldSystemFontOfSize:kDetailsLabelFontSize]; 178 | self.detailsLabelColor = [UIColor whiteColor]; 179 | self.activityIndicatorColor = [UIColor whiteColor]; 180 | self.xOffset = 0.0f; 181 | self.yOffset = 0.0f; 182 | self.dimBackground = NO; 183 | self.margin = 20.0f; 184 | self.cornerRadius = 10.0f; 185 | self.graceTime = 0.0f; 186 | self.minShowTime = 0.0f; 187 | self.removeFromSuperViewOnHide = NO; 188 | self.minSize = CGSizeZero; 189 | self.square = NO; 190 | self.contentMode = UIViewContentModeCenter; 191 | self.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin 192 | | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; 193 | 194 | // Transparent background 195 | self.opaque = NO; 196 | self.backgroundColor = [UIColor clearColor]; 197 | // Make it invisible for now 198 | self.alpha = 0.0f; 199 | 200 | taskInProgress = NO; 201 | rotationTransform = CGAffineTransformIdentity; 202 | 203 | [self setupLabels]; 204 | [self updateIndicators]; 205 | [self registerForKVO]; 206 | [self registerForNotifications]; 207 | } 208 | return self; 209 | } 210 | 211 | - (id)initWithView:(UIView *)view { 212 | NSAssert(view, @"View must not be nil."); 213 | return [self initWithFrame:view.bounds]; 214 | } 215 | 216 | - (id)initWithWindow:(UIWindow *)window { 217 | return [self initWithView:window]; 218 | } 219 | 220 | - (void)dealloc { 221 | [self unregisterFromNotifications]; 222 | [self unregisterFromKVO]; 223 | #if !__has_feature(objc_arc) 224 | [color release]; 225 | [indicator release]; 226 | [label release]; 227 | [detailsLabel release]; 228 | [labelText release]; 229 | [detailsLabelText release]; 230 | [graceTimer release]; 231 | [minShowTimer release]; 232 | [showStarted release]; 233 | [customView release]; 234 | [labelFont release]; 235 | [labelColor release]; 236 | [detailsLabelFont release]; 237 | [detailsLabelColor release]; 238 | #if NS_BLOCKS_AVAILABLE 239 | [completionBlock release]; 240 | #endif 241 | [super dealloc]; 242 | #endif 243 | } 244 | 245 | #pragma mark - Show & hide 246 | 247 | - (void)show:(BOOL)animated { 248 | useAnimation = animated; 249 | // If the grace time is set postpone the HUD display 250 | if (self.graceTime > 0.0) { 251 | self.graceTimer = [NSTimer scheduledTimerWithTimeInterval:self.graceTime target:self 252 | selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; 253 | } 254 | // ... otherwise show the HUD imediately 255 | else { 256 | [self showUsingAnimation:useAnimation]; 257 | } 258 | } 259 | 260 | - (void)hide:(BOOL)animated { 261 | useAnimation = animated; 262 | // If the minShow time is set, calculate how long the hud was shown, 263 | // and pospone the hiding operation if necessary 264 | if (self.minShowTime > 0.0 && showStarted) { 265 | NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:showStarted]; 266 | if (interv < self.minShowTime) { 267 | self.minShowTimer = [NSTimer scheduledTimerWithTimeInterval:(self.minShowTime - interv) target:self 268 | selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; 269 | return; 270 | } 271 | } 272 | // ... otherwise hide the HUD immediately 273 | [self hideUsingAnimation:useAnimation]; 274 | } 275 | 276 | - (void)hide:(BOOL)animated afterDelay:(NSTimeInterval)delay { 277 | [self performSelector:@selector(hideDelayed:) withObject:[NSNumber numberWithBool:animated] afterDelay:delay]; 278 | } 279 | 280 | - (void)hideDelayed:(NSNumber *)animated { 281 | [self hide:[animated boolValue]]; 282 | } 283 | 284 | #pragma mark - Timer callbacks 285 | 286 | - (void)handleGraceTimer:(NSTimer *)theTimer { 287 | // Show the HUD only if the task is still running 288 | if (taskInProgress) { 289 | [self showUsingAnimation:useAnimation]; 290 | } 291 | } 292 | 293 | - (void)handleMinShowTimer:(NSTimer *)theTimer { 294 | [self hideUsingAnimation:useAnimation]; 295 | } 296 | 297 | #pragma mark - View Hierrarchy 298 | 299 | - (void)didMoveToSuperview { 300 | [self updateForCurrentOrientationAnimated:NO]; 301 | } 302 | 303 | #pragma mark - Internal show & hide operations 304 | 305 | - (void)showUsingAnimation:(BOOL)animated { 306 | // Cancel any scheduled hideDelayed: calls 307 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 308 | [self setNeedsDisplay]; 309 | 310 | if (animated && animationType == MBProgressHUDAnimationZoomIn) { 311 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); 312 | } else if (animated && animationType == MBProgressHUDAnimationZoomOut) { 313 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); 314 | } 315 | self.showStarted = [NSDate date]; 316 | // Fade in 317 | if (animated) { 318 | [UIView beginAnimations:nil context:NULL]; 319 | [UIView setAnimationDuration:0.30]; 320 | self.alpha = 1.0f; 321 | if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) { 322 | self.transform = rotationTransform; 323 | } 324 | [UIView commitAnimations]; 325 | } 326 | else { 327 | self.alpha = 1.0f; 328 | } 329 | } 330 | 331 | - (void)hideUsingAnimation:(BOOL)animated { 332 | // Fade out 333 | if (animated && showStarted) { 334 | [UIView beginAnimations:nil context:NULL]; 335 | [UIView setAnimationDuration:0.30]; 336 | [UIView setAnimationDelegate:self]; 337 | [UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)]; 338 | // 0.02 prevents the hud from passing through touches during the animation the hud will get completely hidden 339 | // in the done method 340 | if (animationType == MBProgressHUDAnimationZoomIn) { 341 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f)); 342 | } else if (animationType == MBProgressHUDAnimationZoomOut) { 343 | self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f)); 344 | } 345 | 346 | self.alpha = 0.02f; 347 | [UIView commitAnimations]; 348 | } 349 | else { 350 | self.alpha = 0.0f; 351 | [self done]; 352 | } 353 | self.showStarted = nil; 354 | } 355 | 356 | - (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void*)context { 357 | [self done]; 358 | } 359 | 360 | - (void)done { 361 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 362 | isFinished = YES; 363 | self.alpha = 0.0f; 364 | if (removeFromSuperViewOnHide) { 365 | [self removeFromSuperview]; 366 | } 367 | #if NS_BLOCKS_AVAILABLE 368 | if (self.completionBlock) { 369 | self.completionBlock(); 370 | self.completionBlock = NULL; 371 | } 372 | #endif 373 | if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { 374 | [delegate performSelector:@selector(hudWasHidden:) withObject:self]; 375 | } 376 | } 377 | 378 | #pragma mark - Threading 379 | 380 | - (void)showWhileExecuting:(SEL)method onTarget:(id)target withObject:(id)object animated:(BOOL)animated { 381 | methodForExecution = method; 382 | targetForExecution = MB_RETAIN(target); 383 | objectForExecution = MB_RETAIN(object); 384 | // Launch execution in new thread 385 | self.taskInProgress = YES; 386 | [NSThread detachNewThreadSelector:@selector(launchExecution) toTarget:self withObject:nil]; 387 | // Show HUD view 388 | [self show:animated]; 389 | } 390 | 391 | #if NS_BLOCKS_AVAILABLE 392 | 393 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block { 394 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 395 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 396 | } 397 | 398 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block completionBlock:(void (^)())completion { 399 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 400 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:completion]; 401 | } 402 | 403 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue { 404 | [self showAnimated:animated whileExecutingBlock:block onQueue:queue completionBlock:NULL]; 405 | } 406 | 407 | - (void)showAnimated:(BOOL)animated whileExecutingBlock:(dispatch_block_t)block onQueue:(dispatch_queue_t)queue 408 | completionBlock:(MBProgressHUDCompletionBlock)completion { 409 | self.taskInProgress = YES; 410 | self.completionBlock = completion; 411 | dispatch_async(queue, ^(void) { 412 | block(); 413 | dispatch_async(dispatch_get_main_queue(), ^(void) { 414 | [self cleanUp]; 415 | }); 416 | }); 417 | [self show:animated]; 418 | } 419 | 420 | #endif 421 | 422 | - (void)launchExecution { 423 | @autoreleasepool { 424 | #pragma clang diagnostic push 425 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 426 | // Start executing the requested task 427 | [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; 428 | #pragma clang diagnostic pop 429 | // Task completed, update view in main thread (note: view operations should 430 | // be done only in the main thread) 431 | [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO]; 432 | } 433 | } 434 | 435 | - (void)cleanUp { 436 | taskInProgress = NO; 437 | #if !__has_feature(objc_arc) 438 | [targetForExecution release]; 439 | [objectForExecution release]; 440 | #else 441 | targetForExecution = nil; 442 | objectForExecution = nil; 443 | #endif 444 | [self hide:useAnimation]; 445 | } 446 | 447 | #pragma mark - UI 448 | 449 | - (void)setupLabels { 450 | label = [[UILabel alloc] initWithFrame:self.bounds]; 451 | label.adjustsFontSizeToFitWidth = NO; 452 | label.textAlignment = NSTextAlignmentCenter; 453 | label.opaque = NO; 454 | label.backgroundColor = [UIColor clearColor]; 455 | label.textColor = self.labelColor; 456 | label.font = self.labelFont; 457 | label.text = self.labelText; 458 | [self addSubview:label]; 459 | 460 | detailsLabel = [[UILabel alloc] initWithFrame:self.bounds]; 461 | detailsLabel.font = self.detailsLabelFont; 462 | detailsLabel.adjustsFontSizeToFitWidth = NO; 463 | detailsLabel.textAlignment = NSTextAlignmentCenter; 464 | detailsLabel.opaque = NO; 465 | detailsLabel.backgroundColor = [UIColor clearColor]; 466 | detailsLabel.textColor = self.detailsLabelColor; 467 | detailsLabel.numberOfLines = 0; 468 | detailsLabel.font = self.detailsLabelFont; 469 | detailsLabel.text = self.detailsLabelText; 470 | [self addSubview:detailsLabel]; 471 | } 472 | 473 | - (void)updateIndicators { 474 | 475 | BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; 476 | BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; 477 | 478 | if (mode == MBProgressHUDModeIndeterminate) { 479 | if (!isActivityIndicator) { 480 | // Update to indeterminate indicator 481 | [indicator removeFromSuperview]; 482 | self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc] 483 | initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]); 484 | [(UIActivityIndicatorView *)indicator startAnimating]; 485 | [self addSubview:indicator]; 486 | } 487 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 488 | [(UIActivityIndicatorView *)indicator setColor:self.activityIndicatorColor]; 489 | #endif 490 | } 491 | else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { 492 | // Update to bar determinate indicator 493 | [indicator removeFromSuperview]; 494 | self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]); 495 | [self addSubview:indicator]; 496 | } 497 | else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { 498 | if (!isRoundIndicator) { 499 | // Update to determinante indicator 500 | [indicator removeFromSuperview]; 501 | self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]); 502 | [self addSubview:indicator]; 503 | } 504 | if (mode == MBProgressHUDModeAnnularDeterminate) { 505 | [(MBRoundProgressView *)indicator setAnnular:YES]; 506 | } 507 | } 508 | else if (mode == MBProgressHUDModeCustomView && customView != indicator) { 509 | // Update custom view indicator 510 | [indicator removeFromSuperview]; 511 | self.indicator = customView; 512 | [self addSubview:indicator]; 513 | } else if (mode == MBProgressHUDModeText) { 514 | [indicator removeFromSuperview]; 515 | self.indicator = nil; 516 | } 517 | } 518 | 519 | #pragma mark - Layout 520 | 521 | - (void)layoutSubviews { 522 | [super layoutSubviews]; 523 | 524 | // Entirely cover the parent view 525 | UIView *parent = self.superview; 526 | if (parent) { 527 | self.frame = parent.bounds; 528 | } 529 | CGRect bounds = self.bounds; 530 | 531 | // Determine the total widt and height needed 532 | CGFloat maxWidth = bounds.size.width - 4 * margin; 533 | CGSize totalSize = CGSizeZero; 534 | 535 | CGRect indicatorF = indicator.bounds; 536 | indicatorF.size.width = MIN(indicatorF.size.width, maxWidth); 537 | totalSize.width = MAX(totalSize.width, indicatorF.size.width); 538 | totalSize.height += indicatorF.size.height; 539 | 540 | CGSize labelSize = MB_TEXTSIZE(label.text, label.font); 541 | labelSize.width = MIN(labelSize.width, maxWidth); 542 | totalSize.width = MAX(totalSize.width, labelSize.width); 543 | totalSize.height += labelSize.height; 544 | if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { 545 | totalSize.height += kPadding; 546 | } 547 | 548 | CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin; 549 | CGSize maxSize = CGSizeMake(maxWidth, remainingHeight); 550 | CGSize detailsLabelSize = MB_MULTILINE_TEXTSIZE(detailsLabel.text, detailsLabel.font, maxSize, detailsLabel.lineBreakMode); 551 | totalSize.width = MAX(totalSize.width, detailsLabelSize.width); 552 | totalSize.height += detailsLabelSize.height; 553 | if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { 554 | totalSize.height += kPadding; 555 | } 556 | 557 | totalSize.width += 2 * margin; 558 | totalSize.height += 2 * margin; 559 | 560 | // Position elements 561 | CGFloat yPos = round(((bounds.size.height - totalSize.height) / 2)) + margin + yOffset; 562 | CGFloat xPos = xOffset; 563 | indicatorF.origin.y = yPos; 564 | indicatorF.origin.x = round((bounds.size.width - indicatorF.size.width) / 2) + xPos; 565 | indicator.frame = indicatorF; 566 | yPos += indicatorF.size.height; 567 | 568 | if (labelSize.height > 0.f && indicatorF.size.height > 0.f) { 569 | yPos += kPadding; 570 | } 571 | CGRect labelF; 572 | labelF.origin.y = yPos; 573 | labelF.origin.x = round((bounds.size.width - labelSize.width) / 2) + xPos; 574 | labelF.size = labelSize; 575 | label.frame = labelF; 576 | yPos += labelF.size.height; 577 | 578 | if (detailsLabelSize.height > 0.f && (indicatorF.size.height > 0.f || labelSize.height > 0.f)) { 579 | yPos += kPadding; 580 | } 581 | CGRect detailsLabelF; 582 | detailsLabelF.origin.y = yPos; 583 | detailsLabelF.origin.x = round((bounds.size.width - detailsLabelSize.width) / 2) + xPos; 584 | detailsLabelF.size = detailsLabelSize; 585 | detailsLabel.frame = detailsLabelF; 586 | 587 | // Enforce minsize and quare rules 588 | if (square) { 589 | CGFloat max = MAX(totalSize.width, totalSize.height); 590 | if (max <= bounds.size.width - 2 * margin) { 591 | totalSize.width = max; 592 | } 593 | if (max <= bounds.size.height - 2 * margin) { 594 | totalSize.height = max; 595 | } 596 | } 597 | if (totalSize.width < minSize.width) { 598 | totalSize.width = minSize.width; 599 | } 600 | if (totalSize.height < minSize.height) { 601 | totalSize.height = minSize.height; 602 | } 603 | 604 | size = totalSize; 605 | } 606 | 607 | #pragma mark BG Drawing 608 | 609 | - (void)drawRect:(CGRect)rect { 610 | 611 | CGContextRef context = UIGraphicsGetCurrentContext(); 612 | UIGraphicsPushContext(context); 613 | 614 | if (self.dimBackground) { 615 | //Gradient colours 616 | size_t gradLocationsNum = 2; 617 | CGFloat gradLocations[2] = {0.0f, 1.0f}; 618 | CGFloat gradColors[8] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.75f}; 619 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 620 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, gradColors, gradLocations, gradLocationsNum); 621 | CGColorSpaceRelease(colorSpace); 622 | //Gradient center 623 | CGPoint gradCenter= CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 624 | //Gradient radius 625 | float gradRadius = MIN(self.bounds.size.width , self.bounds.size.height) ; 626 | //Gradient draw 627 | CGContextDrawRadialGradient (context, gradient, gradCenter, 628 | 0, gradCenter, gradRadius, 629 | kCGGradientDrawsAfterEndLocation); 630 | CGGradientRelease(gradient); 631 | } 632 | 633 | // Set background rect color 634 | if (self.color) { 635 | CGContextSetFillColorWithColor(context, self.color.CGColor); 636 | } else { 637 | CGContextSetGrayFillColor(context, 0.0f, self.opacity); 638 | } 639 | 640 | 641 | // Center HUD 642 | CGRect allRect = self.bounds; 643 | // Draw rounded HUD backgroud rect 644 | CGRect boxRect = CGRectMake(round((allRect.size.width - size.width) / 2) + self.xOffset, 645 | round((allRect.size.height - size.height) / 2) + self.yOffset, size.width, size.height); 646 | float radius = self.cornerRadius; 647 | CGContextBeginPath(context); 648 | CGContextMoveToPoint(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect)); 649 | CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMinY(boxRect) + radius, radius, 3 * (float)M_PI / 2, 0, 0); 650 | CGContextAddArc(context, CGRectGetMaxX(boxRect) - radius, CGRectGetMaxY(boxRect) - radius, radius, 0, (float)M_PI / 2, 0); 651 | CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMaxY(boxRect) - radius, radius, (float)M_PI / 2, (float)M_PI, 0); 652 | CGContextAddArc(context, CGRectGetMinX(boxRect) + radius, CGRectGetMinY(boxRect) + radius, radius, (float)M_PI, 3 * (float)M_PI / 2, 0); 653 | CGContextClosePath(context); 654 | CGContextFillPath(context); 655 | 656 | UIGraphicsPopContext(); 657 | } 658 | 659 | #pragma mark - KVO 660 | 661 | - (void)registerForKVO { 662 | for (NSString *keyPath in [self observableKeypaths]) { 663 | [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 664 | } 665 | } 666 | 667 | - (void)unregisterFromKVO { 668 | for (NSString *keyPath in [self observableKeypaths]) { 669 | [self removeObserver:self forKeyPath:keyPath]; 670 | } 671 | } 672 | 673 | - (NSArray *)observableKeypaths { 674 | return [NSArray arrayWithObjects:@"mode", @"customView", @"labelText", @"labelFont", @"labelColor", 675 | @"detailsLabelText", @"detailsLabelFont", @"detailsLabelColor", @"progress", @"activityIndicatorColor", nil]; 676 | } 677 | 678 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 679 | if (![NSThread isMainThread]) { 680 | [self performSelectorOnMainThread:@selector(updateUIForKeypath:) withObject:keyPath waitUntilDone:NO]; 681 | } else { 682 | [self updateUIForKeypath:keyPath]; 683 | } 684 | } 685 | 686 | - (void)updateUIForKeypath:(NSString *)keyPath { 687 | if ([keyPath isEqualToString:@"mode"] || [keyPath isEqualToString:@"customView"] || 688 | [keyPath isEqualToString:@"activityIndicatorColor"]) { 689 | [self updateIndicators]; 690 | } else if ([keyPath isEqualToString:@"labelText"]) { 691 | label.text = self.labelText; 692 | } else if ([keyPath isEqualToString:@"labelFont"]) { 693 | label.font = self.labelFont; 694 | } else if ([keyPath isEqualToString:@"labelColor"]) { 695 | label.textColor = self.labelColor; 696 | } else if ([keyPath isEqualToString:@"detailsLabelText"]) { 697 | detailsLabel.text = self.detailsLabelText; 698 | } else if ([keyPath isEqualToString:@"detailsLabelFont"]) { 699 | detailsLabel.font = self.detailsLabelFont; 700 | } else if ([keyPath isEqualToString:@"detailsLabelColor"]) { 701 | detailsLabel.textColor = self.detailsLabelColor; 702 | } else if ([keyPath isEqualToString:@"progress"]) { 703 | if ([indicator respondsToSelector:@selector(setProgress:)]) { 704 | [(id)indicator setValue:@(progress) forKey:@"progress"]; 705 | } 706 | return; 707 | } 708 | [self setNeedsLayout]; 709 | [self setNeedsDisplay]; 710 | } 711 | 712 | #pragma mark - Notifications 713 | 714 | - (void)registerForNotifications { 715 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 716 | 717 | [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) 718 | name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 719 | } 720 | 721 | - (void)unregisterFromNotifications { 722 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 723 | [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; 724 | } 725 | 726 | - (void)statusBarOrientationDidChange:(NSNotification *)notification { 727 | UIView *superview = self.superview; 728 | if (!superview) { 729 | return; 730 | } else { 731 | [self updateForCurrentOrientationAnimated:YES]; 732 | } 733 | } 734 | 735 | - (void)updateForCurrentOrientationAnimated:(BOOL)animated { 736 | // Stay in sync with the superview in any case 737 | if (self.superview) { 738 | self.bounds = self.superview.bounds; 739 | [self setNeedsDisplay]; 740 | } 741 | 742 | // Not needed on iOS 8+, compile out when the deployment target allows, 743 | // to avoid sharedApplication problems on extension targets 744 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 745 | // Only needed pre iOS 7 when added to a window 746 | BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0; 747 | if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return; 748 | 749 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 750 | CGFloat radians = 0; 751 | if (UIInterfaceOrientationIsLandscape(orientation)) { 752 | if (orientation == UIInterfaceOrientationLandscapeLeft) { radians = -(CGFloat)M_PI_2; } 753 | else { radians = (CGFloat)M_PI_2; } 754 | // Window coordinates differ! 755 | self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); 756 | } else { 757 | if (orientation == UIInterfaceOrientationPortraitUpsideDown) { radians = (CGFloat)M_PI; } 758 | else { radians = 0; } 759 | } 760 | rotationTransform = CGAffineTransformMakeRotation(radians); 761 | 762 | if (animated) { 763 | [UIView beginAnimations:nil context:nil]; 764 | [UIView setAnimationDuration:0.3]; 765 | } 766 | [self setTransform:rotationTransform]; 767 | if (animated) { 768 | [UIView commitAnimations]; 769 | } 770 | #endif 771 | } 772 | 773 | @end 774 | 775 | 776 | @implementation MBRoundProgressView 777 | 778 | #pragma mark - Lifecycle 779 | 780 | - (id)init { 781 | return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; 782 | } 783 | 784 | - (id)initWithFrame:(CGRect)frame { 785 | self = [super initWithFrame:frame]; 786 | if (self) { 787 | self.backgroundColor = [UIColor clearColor]; 788 | self.opaque = NO; 789 | _progress = 0.f; 790 | _annular = NO; 791 | _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; 792 | _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; 793 | [self registerForKVO]; 794 | } 795 | return self; 796 | } 797 | 798 | - (void)dealloc { 799 | [self unregisterFromKVO]; 800 | #if !__has_feature(objc_arc) 801 | [_progressTintColor release]; 802 | [_backgroundTintColor release]; 803 | [super dealloc]; 804 | #endif 805 | } 806 | 807 | #pragma mark - Drawing 808 | 809 | - (void)drawRect:(CGRect)rect { 810 | 811 | CGRect allRect = self.bounds; 812 | CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f); 813 | CGContextRef context = UIGraphicsGetCurrentContext(); 814 | 815 | if (_annular) { 816 | // Draw background 817 | BOOL isPreiOS7 = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0; 818 | CGFloat lineWidth = isPreiOS7 ? 5.f : 2.f; 819 | UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; 820 | processBackgroundPath.lineWidth = lineWidth; 821 | processBackgroundPath.lineCapStyle = kCGLineCapButt; 822 | CGPoint center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 823 | CGFloat radius = (self.bounds.size.width - lineWidth)/2; 824 | CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees 825 | CGFloat endAngle = (2 * (float)M_PI) + startAngle; 826 | [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 827 | [_backgroundTintColor set]; 828 | [processBackgroundPath stroke]; 829 | // Draw progress 830 | UIBezierPath *processPath = [UIBezierPath bezierPath]; 831 | processPath.lineCapStyle = isPreiOS7 ? kCGLineCapRound : kCGLineCapSquare; 832 | processPath.lineWidth = lineWidth; 833 | endAngle = (self.progress * 2 * (float)M_PI) + startAngle; 834 | [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; 835 | [_progressTintColor set]; 836 | [processPath stroke]; 837 | } else { 838 | // Draw background 839 | [_progressTintColor setStroke]; 840 | [_backgroundTintColor setFill]; 841 | CGContextSetLineWidth(context, 2.0f); 842 | CGContextFillEllipseInRect(context, circleRect); 843 | CGContextStrokeEllipseInRect(context, circleRect); 844 | // Draw progress 845 | CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2); 846 | CGFloat radius = (allRect.size.width - 4) / 2; 847 | CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees 848 | CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle; 849 | [_progressTintColor setFill]; 850 | CGContextMoveToPoint(context, center.x, center.y); 851 | CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); 852 | CGContextClosePath(context); 853 | CGContextFillPath(context); 854 | } 855 | } 856 | 857 | #pragma mark - KVO 858 | 859 | - (void)registerForKVO { 860 | for (NSString *keyPath in [self observableKeypaths]) { 861 | [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 862 | } 863 | } 864 | 865 | - (void)unregisterFromKVO { 866 | for (NSString *keyPath in [self observableKeypaths]) { 867 | [self removeObserver:self forKeyPath:keyPath]; 868 | } 869 | } 870 | 871 | - (NSArray *)observableKeypaths { 872 | return [NSArray arrayWithObjects:@"progressTintColor", @"backgroundTintColor", @"progress", @"annular", nil]; 873 | } 874 | 875 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 876 | [self setNeedsDisplay]; 877 | } 878 | 879 | @end 880 | 881 | 882 | @implementation MBBarProgressView 883 | 884 | #pragma mark - Lifecycle 885 | 886 | - (id)init { 887 | return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)]; 888 | } 889 | 890 | - (id)initWithFrame:(CGRect)frame { 891 | self = [super initWithFrame:frame]; 892 | if (self) { 893 | _progress = 0.f; 894 | _lineColor = [UIColor whiteColor]; 895 | _progressColor = [UIColor whiteColor]; 896 | _progressRemainingColor = [UIColor clearColor]; 897 | self.backgroundColor = [UIColor clearColor]; 898 | self.opaque = NO; 899 | [self registerForKVO]; 900 | } 901 | return self; 902 | } 903 | 904 | - (void)dealloc { 905 | [self unregisterFromKVO]; 906 | #if !__has_feature(objc_arc) 907 | [_lineColor release]; 908 | [_progressColor release]; 909 | [_progressRemainingColor release]; 910 | [super dealloc]; 911 | #endif 912 | } 913 | 914 | #pragma mark - Drawing 915 | 916 | - (void)drawRect:(CGRect)rect { 917 | CGContextRef context = UIGraphicsGetCurrentContext(); 918 | 919 | CGContextSetLineWidth(context, 2); 920 | CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]); 921 | CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]); 922 | 923 | // Draw background 924 | float radius = (rect.size.height / 2) - 2; 925 | CGContextMoveToPoint(context, 2, rect.size.height/2); 926 | CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); 927 | CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); 928 | CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); 929 | CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); 930 | CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); 931 | CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); 932 | CGContextFillPath(context); 933 | 934 | // Draw border 935 | CGContextMoveToPoint(context, 2, rect.size.height/2); 936 | CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); 937 | CGContextAddLineToPoint(context, rect.size.width - radius - 2, 2); 938 | CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); 939 | CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); 940 | CGContextAddLineToPoint(context, radius + 2, rect.size.height - 2); 941 | CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); 942 | CGContextStrokePath(context); 943 | 944 | CGContextSetFillColorWithColor(context, [_progressColor CGColor]); 945 | radius = radius - 2; 946 | float amount = self.progress * rect.size.width; 947 | 948 | // Progress in the middle area 949 | if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) { 950 | CGContextMoveToPoint(context, 4, rect.size.height/2); 951 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 952 | CGContextAddLineToPoint(context, amount, 4); 953 | CGContextAddLineToPoint(context, amount, radius + 4); 954 | 955 | CGContextMoveToPoint(context, 4, rect.size.height/2); 956 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 957 | CGContextAddLineToPoint(context, amount, rect.size.height - 4); 958 | CGContextAddLineToPoint(context, amount, radius + 4); 959 | 960 | CGContextFillPath(context); 961 | } 962 | 963 | // Progress in the right arc 964 | else if (amount > radius + 4) { 965 | float x = amount - (rect.size.width - radius - 4); 966 | 967 | CGContextMoveToPoint(context, 4, rect.size.height/2); 968 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 969 | CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4); 970 | float angle = -acos(x/radius); 971 | if (isnan(angle)) angle = 0; 972 | CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0); 973 | CGContextAddLineToPoint(context, amount, rect.size.height/2); 974 | 975 | CGContextMoveToPoint(context, 4, rect.size.height/2); 976 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 977 | CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4); 978 | angle = acos(x/radius); 979 | if (isnan(angle)) angle = 0; 980 | CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1); 981 | CGContextAddLineToPoint(context, amount, rect.size.height/2); 982 | 983 | CGContextFillPath(context); 984 | } 985 | 986 | // Progress is in the left arc 987 | else if (amount < radius + 4 && amount > 0) { 988 | CGContextMoveToPoint(context, 4, rect.size.height/2); 989 | CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); 990 | CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); 991 | 992 | CGContextMoveToPoint(context, 4, rect.size.height/2); 993 | CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); 994 | CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); 995 | 996 | CGContextFillPath(context); 997 | } 998 | } 999 | 1000 | #pragma mark - KVO 1001 | 1002 | - (void)registerForKVO { 1003 | for (NSString *keyPath in [self observableKeypaths]) { 1004 | [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; 1005 | } 1006 | } 1007 | 1008 | - (void)unregisterFromKVO { 1009 | for (NSString *keyPath in [self observableKeypaths]) { 1010 | [self removeObserver:self forKeyPath:keyPath]; 1011 | } 1012 | } 1013 | 1014 | - (NSArray *)observableKeypaths { 1015 | return [NSArray arrayWithObjects:@"lineColor", @"progressRemainingColor", @"progressColor", @"progress", nil]; 1016 | } 1017 | 1018 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 1019 | [self setNeedsDisplay]; 1020 | } 1021 | 1022 | @end 1023 | -------------------------------------------------------------------------------- /src/Views/THFacePickerCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // THFacePickerCollectionViewCell.h 3 | // THVideoFaceSwapper 4 | // 5 | // Created by Clayton Rieck on 4/21/15. 6 | // 7 | // 8 | 9 | @interface THFacePickerCollectionViewCell : UICollectionViewCell 10 | 11 | @property (strong, nonatomic) NSIndexPath *currentIndexPath; 12 | @property (nonatomic, assign, readonly) BOOL highlightSelected;; 13 | 14 | - (void)startLoading; 15 | - (void)stopLoading; 16 | - (void)clearImage; 17 | - (void)highlightSelected:(BOOL)highlight; 18 | - (void)setFaceWithImage:(UIImage *)faceImage atIndexPath:(NSIndexPath *)indexPath; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /src/Views/THFacePickerCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // THFacePickerCollectionViewCell.m 3 | // THVideoFaceSwapper 4 | // 5 | // Created by Clayton Rieck on 4/21/15. 6 | // 7 | // 8 | 9 | #import "THFacePickerCollectionViewCell.h" 10 | 11 | @interface THFacePickerCollectionViewCell () 12 | 13 | @property (nonatomic) UIImageView *faceImageView; 14 | @property (nonatomic) UIActivityIndicatorView *loadingIndicatorView; 15 | @property (nonatomic) UIView *selectedView; 16 | 17 | @property (nonatomic, assign, readwrite) BOOL highlightSelected; 18 | 19 | @end 20 | 21 | @implementation THFacePickerCollectionViewCell 22 | 23 | - (instancetype)initWithFrame:(CGRect)frame 24 | { 25 | self = [super initWithFrame:frame]; 26 | if ( self ) { 27 | _faceImageView = [[UIImageView alloc] initWithFrame:frame]; 28 | _faceImageView.contentMode = UIViewContentModeScaleAspectFill; 29 | _faceImageView.center = self.contentView.center; 30 | [self.contentView addSubview:_faceImageView]; 31 | 32 | _loadingIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; 33 | _loadingIndicatorView.center = self.contentView.center; 34 | [self.contentView addSubview:_loadingIndicatorView]; 35 | 36 | _selectedView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, frame.size.width, frame.size.height)]; 37 | _selectedView.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.8f]; 38 | _selectedView.layer.borderColor = [UIColor whiteColor].CGColor; 39 | _selectedView.layer.borderWidth = 3.0f; 40 | _selectedView.alpha = 0.0f; 41 | [self.contentView addSubview:_selectedView]; 42 | 43 | _highlightSelected = NO; 44 | 45 | self.clipsToBounds = YES; 46 | } 47 | 48 | return self; 49 | } 50 | 51 | #pragma mark - Public 52 | 53 | - (void)startLoading 54 | { 55 | [self.loadingIndicatorView startAnimating]; 56 | self.loadingIndicatorView.hidden = NO; 57 | } 58 | 59 | - (void)stopLoading 60 | { 61 | [self.loadingIndicatorView stopAnimating]; 62 | self.loadingIndicatorView.hidden = YES; 63 | } 64 | 65 | - (void)clearImage 66 | { 67 | self.faceImageView.image = nil; 68 | } 69 | 70 | - (void)setFaceWithImage:(UIImage *)faceImage atIndexPath:(NSIndexPath *)indexPath 71 | { 72 | if ( [indexPath isEqual:self.currentIndexPath] ) { 73 | self.faceImageView.image = faceImage; 74 | } 75 | } 76 | 77 | - (void)highlightSelected:(BOOL)highlight 78 | { 79 | self.highlightSelected = highlight; 80 | 81 | CGFloat targetAlhpa; 82 | if ( highlight ) { 83 | targetAlhpa = 1.0f; 84 | } 85 | else { 86 | targetAlhpa = 0.0f; 87 | } 88 | 89 | [UIView animateWithDuration:0.1f animations:^{ 90 | self.selectedView.alpha = targetAlhpa; 91 | }]; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /src/Views/THFacesCollectionReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // THFacesCollectionReusableView.h 3 | // THVideoFaceSwapper 4 | // 5 | // Created by Clayton Rieck on 4/22/15. 6 | // 7 | // 8 | 9 | @interface THFacesCollectionReusableView : UICollectionReusableView 10 | 11 | @property (nonatomic, assign) NSString *title; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /src/Views/THFacesCollectionReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // THFacesCollectionReusableView.m 3 | // THVideoFaceSwapper 4 | // 5 | // Created by Clayton Rieck on 4/22/15. 6 | // 7 | // 8 | 9 | #import "THFacesCollectionReusableView.h" 10 | 11 | @interface THFacesCollectionReusableView () 12 | 13 | @property (nonatomic) UILabel *titleLabel; 14 | 15 | @end 16 | 17 | @implementation THFacesCollectionReusableView 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame 20 | { 21 | self = [super initWithFrame:frame]; 22 | if ( self ) { 23 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height)]; 24 | _titleLabel.font = [UIFont systemFontOfSize:20.0f]; 25 | _titleLabel.numberOfLines = 1; 26 | [self addSubview:_titleLabel]; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)setTitle:(NSString *)title 32 | { 33 | _title = title; 34 | self.titleLabel.text = title; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /src/main.mm: -------------------------------------------------------------------------------- 1 | #include "ofMain.h" 2 | #include "ofApp.h" 3 | 4 | int main(){ 5 | ofiOSWindowSettings settings; 6 | settings.enableRetina = false; // enables retina resolution if the device supports it. 7 | settings.enableDepth = false; // enables depth buffer for 3d drawing. 8 | settings.enableAntiAliasing = false; // enables anti-aliasing which smooths out graphics on the screen. 9 | settings.numOfAntiAliasingSamples = 0; // number of samples used for anti-aliasing. 10 | settings.enableHardwareOrientation = false; // enables native view orientation. 11 | settings.enableHardwareOrientationAnimation = false; // enables native orientation changes to be animated. 12 | settings.glesVersion = OFXIOS_RENDERER_ES1; // type of renderer to use, ES1, ES2, ES3 13 | settings.windowMode = OF_FULLSCREEN; 14 | ofCreateWindow(settings); 15 | 16 | return ofRunApp(new ofApp); 17 | } 18 | -------------------------------------------------------------------------------- /src/ofApp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ofMain.h" 4 | #include "ofxiOS.h" 5 | #include "ofxiOSExtras.h" 6 | #include "ofxOpenCv.h" 7 | #include "ofxCv.h" 8 | #include "Clone.h" 9 | #include "ofxFaceTracker.h" 10 | #include "ofxFaceTrackerThreaded.h" 11 | 12 | #include "THPhotoPickerViewController.h" 13 | 14 | using namespace ofxCv; 15 | using namespace cv; 16 | 17 | class ofApp : public ofxiOSApp { 18 | 19 | public: 20 | void setup(); 21 | void update(); 22 | void draw(); 23 | void exit(); 24 | 25 | void touchDown(ofTouchEventArgs & touch); 26 | void touchMoved(ofTouchEventArgs & touch); 27 | void touchUp(ofTouchEventArgs & touch); 28 | void touchDoubleTap(ofTouchEventArgs & touch); 29 | void touchCancelled(ofTouchEventArgs & touch); 30 | 31 | void lostFocus(); 32 | void gotFocus(); 33 | void gotMemoryWarning(); 34 | void deviceOrientationChanged(int newOrientation); 35 | 36 | void setupCam(int width, int height); 37 | void dragEvent(ofDragInfo dragInfo); 38 | void loadFace(string face); 39 | void loadOFImage(ofImage image); 40 | 41 | ofxFaceTrackerThreaded camTracker; 42 | ofVideoGrabber cam; 43 | ofxCvColorImage colorCv; 44 | ofxCvColorImage srcColorCv; 45 | 46 | ofxFaceTracker srcTracker; 47 | ofImage src; 48 | vector srcPoints; 49 | 50 | bool cloneReady; 51 | Clone clone; 52 | ofFbo srcFbo, maskFbo; 53 | 54 | ofDirectory faces; 55 | }; 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/ofApp.mm: -------------------------------------------------------------------------------- 1 | #include "ofApp.h" 2 | 3 | THPhotoPickerViewController *photoPicker; 4 | 5 | //-------------------------------------------------------------- 6 | void ofApp::setup(){ 7 | cout << ofxiOSGetUIWindow().bounds.size.width << " : " << ofxiOSGetUIWindow().bounds.size.height << endl; 8 | faces.allowExt("jpg"); 9 | faces.allowExt("jpeg"); 10 | faces.allowExt("png"); 11 | faces.open("faces/"); 12 | faces.listDir(); 13 | 14 | ofSetVerticalSync(true); 15 | cloneReady = false; 16 | 17 | photoPicker = [[THPhotoPickerViewController alloc] init]; 18 | 19 | int screenWidth = [UIScreen mainScreen].bounds.size.width; 20 | int screenHeight = [UIScreen mainScreen].bounds.size.height; 21 | setupCam(screenWidth, screenHeight); 22 | 23 | ofFbo::Settings settings; 24 | settings.width = cam.getWidth(); 25 | settings.height = cam.getHeight(); 26 | maskFbo.allocate(settings); 27 | srcFbo.allocate(settings); 28 | clone.setup(cam.getWidth(), cam.getHeight()); 29 | 30 | camTracker.setup(); 31 | srcTracker.setup(); 32 | srcTracker.setIterations(15); 33 | srcTracker.setAttempts(4); 34 | 35 | if ( faces.size() > 0 ) { 36 | loadFace(faces.getPath(0)); 37 | } 38 | 39 | colorCv.allocate(cam.getWidth(), cam.getHeight()); 40 | } 41 | 42 | //-------------------------------------------------------------- 43 | void ofApp::update(){ 44 | cam.update(); 45 | if(cam.isFrameNew()) { 46 | colorCv = cam.getPixels(); 47 | camTracker.update(toCv(colorCv)); 48 | 49 | cloneReady = camTracker.getFound(); 50 | if(cloneReady) { 51 | ofMesh camMesh = camTracker.getImageMesh(); 52 | camMesh.clearTexCoords(); 53 | camMesh.addTexCoords(srcPoints); 54 | 55 | maskFbo.begin(); 56 | ofClear(0, 255); 57 | camMesh.draw(); 58 | maskFbo.end(); 59 | 60 | srcFbo.begin(); 61 | ofClear(0, 255); 62 | src.bind(); 63 | camMesh.draw(); 64 | src.unbind(); 65 | srcFbo.end(); 66 | 67 | clone.setStrength(16); 68 | clone.update(srcFbo.getTextureReference(), cam.getTextureReference(), maskFbo.getTextureReference()); 69 | } 70 | } 71 | } 72 | 73 | //-------------------------------------------------------------- 74 | void ofApp::draw(){ 75 | ofSetColor(255); 76 | cam.draw(0, 0); 77 | 78 | if ( src.getWidth() == 0 ) { 79 | ofDrawBitmapStringHighlight("SELECT OR TAKE AN IMAGE", 10, 30); 80 | } 81 | else if( !srcTracker.getFound() ) { 82 | ofDrawBitmapStringHighlight("SELECTED IMAGE FACE CANNOT BE FOUND", 10, 30); 83 | } 84 | else if ( !camTracker.getFound() ) { 85 | ofDrawBitmapStringHighlight("CAMERA FACE NOT FOUND", 10, 30); 86 | } 87 | else { 88 | if( cloneReady ) { 89 | clone.draw(0, 0); 90 | ofMesh objectMesh = camTracker.getObjectMesh(); 91 | for(int i=0; i< objectMesh.getTexCoords().size(); i++) { 92 | ofVec2f & texCoord = objectMesh.getTexCoords()[i]; 93 | texCoord.x /= ofNextPow2(cam.getWidth()); 94 | texCoord.y /= ofNextPow2(cam.getHeight()); 95 | } 96 | 97 | ofMesh imgMesh = srcTracker.getObjectMesh(); 98 | for(int i=0; i< imgMesh.getTexCoords().size(); i++) { 99 | ofVec2f & texCoord = imgMesh.getTexCoords()[i]; 100 | texCoord.x /= ofNextPow2(src.getWidth()); 101 | texCoord.y /= ofNextPow2(src.getHeight()); 102 | } 103 | 104 | for(int i = 0; i < objectMesh.getNumVertices();i++){ 105 | ofVec3f vertex = objectMesh.getVertex(i); 106 | imgMesh.setVertex(i, vertex); 107 | } 108 | 109 | ofVec2f position = camTracker.getPosition(); 110 | float scale = camTracker.getScale(); 111 | ofVec3f orientation = camTracker.getOrientation(); 112 | ofPushMatrix(); 113 | ofTranslate(position.x, position.y); 114 | ofScale(scale, scale, scale); 115 | ofRotateX(orientation.x * 45.0f); 116 | ofRotateY(orientation.y * 45.0f); 117 | ofRotateZ(orientation.z * 45.0f); 118 | ofSetColor(255,255,255,255); 119 | src.getTextureReference().bind(); 120 | imgMesh.draw(); 121 | src.getTextureReference().unbind(); 122 | ofPopMatrix(); 123 | src.draw(0, 0, 100, 100); 124 | } 125 | } 126 | } 127 | 128 | void ofApp::loadFace(string face){ 129 | cloneReady = false; 130 | src.clear(); 131 | src.loadImage(face); 132 | 133 | if(src.getWidth() > 0) { 134 | Mat cvImage = toCv(src); 135 | srcTracker.update(cvImage); 136 | srcPoints = srcTracker.getImagePoints(); 137 | cloneReady = true; 138 | } 139 | 140 | } 141 | 142 | void ofApp::loadOFImage(ofImage input) { 143 | 144 | cloneReady = false; 145 | src.clear(); 146 | srcPoints.clear(); 147 | srcTracker.setup(); 148 | 149 | if(input.getWidth() > 0) { 150 | 151 | if(input.getWidth() > input.getHeight()){ 152 | 153 | input.resize(ofGetWidth(), input.getHeight()*ofGetWidth() /input.getWidth()); 154 | } 155 | else{ 156 | 157 | input.resize(input.getWidth()*ofGetHeight()/input.getHeight(), ofGetHeight()); 158 | } 159 | 160 | src = input; 161 | Mat cvImage = toCv(input); 162 | srcTracker.update(cvImage); 163 | srcPoints = srcTracker.getImagePoints(); 164 | cloneReady = true; 165 | } 166 | } 167 | 168 | void ofApp::setupCam(int width, int height) { 169 | 170 | cam.setDesiredFrameRate(24); 171 | 172 | if ( cam.listDevices().size() > 1 ) { 173 | cam.setDeviceID(1); // front facing camera 174 | } 175 | else { 176 | cam.setDeviceID(0); // rear facing camera 177 | } 178 | 179 | cam.initGrabber(width, height); 180 | 181 | 182 | if ( !camTracker.isThreadRunning() ) { 183 | camTracker.startThread(); 184 | } 185 | } 186 | 187 | void ofApp::dragEvent(ofDragInfo dragInfo) { 188 | 189 | } 190 | 191 | //-------------------------------------------------------------- 192 | void ofApp::exit(){ 193 | camTracker.waitForThread(); 194 | } 195 | 196 | //-------------------------------------------------------------- 197 | void ofApp::touchDown(ofTouchEventArgs & touch){ 198 | } 199 | 200 | //-------------------------------------------------------------- 201 | void ofApp::touchMoved(ofTouchEventArgs & touch){ 202 | 203 | } 204 | 205 | //-------------------------------------------------------------- 206 | void ofApp::touchUp(ofTouchEventArgs & touch){ 207 | 208 | } 209 | 210 | //-------------------------------------------------------------- 211 | void ofApp::touchDoubleTap(ofTouchEventArgs & touch){ 212 | UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:photoPicker]; 213 | UIViewController *vc = (UIViewController *)ofxiOSGetViewController(); 214 | [vc presentViewController:navController animated:YES completion:^{ 215 | cam.close(); 216 | camTracker.stopThread(); 217 | }]; 218 | } 219 | 220 | //-------------------------------------------------------------- 221 | void ofApp::touchCancelled(ofTouchEventArgs & touch){ 222 | 223 | } 224 | 225 | //-------------------------------------------------------------- 226 | void ofApp::lostFocus(){ 227 | 228 | } 229 | 230 | //-------------------------------------------------------------- 231 | void ofApp::gotFocus(){ 232 | 233 | } 234 | 235 | //-------------------------------------------------------------- 236 | void ofApp::gotMemoryWarning(){ 237 | 238 | } 239 | 240 | //-------------------------------------------------------------- 241 | void ofApp::deviceOrientationChanged(int newOrientation){ 242 | 243 | } 244 | --------------------------------------------------------------------------------