1//
2//  EXFaceDetector.m
3//  Exponent
4//
5//  Created by Stanisław Chmiela on 13.10.2017.
6//  Copyright © 2017 650 Industries. All rights reserved.
7//
8
9#import <EXFaceDetector/EXFaceDetectorModule.h>
10#import <EXFaceDetector/EXFaceDetector.h>
11#import <ExpoModulesCore/EXFileSystemInterface.h>
12#import <EXFaceDetector/EXFaceDetectorUtils.h>
13#import <ExpoModulesCore/EXModuleRegistry.h>
14#import <EXFaceDetector/EXFaceEncoder.h>
15#import <EXFaceDetector/EXCSBufferOrientationCalculator.h>
16
17@interface EXFaceDetectorModule ()
18
19@property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
20
21@end
22
23@implementation EXFaceDetectorModule
24
25static NSFileManager *fileManager = nil;
26static NSDictionary *defaultDetectorOptions = nil;
27
28- (instancetype)initWithModuleRegistry:(EXModuleRegistry *)moduleRegistry
29{
30  self = [super init];
31  if (self) {
32    _moduleRegistry = moduleRegistry;
33    fileManager = [NSFileManager defaultManager];
34  }
35  return self;
36}
37
38EX_EXPORT_MODULE(ExpoFaceDetector);
39
40- (NSDictionary *)constantsToExport
41{
42  return [EXFaceDetectorUtils constantsToExport];
43}
44
45- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
46{
47  _moduleRegistry = moduleRegistry;
48}
49
50EX_EXPORT_METHOD_AS(detectFaces, detectFaces:(nonnull NSDictionary *)options resolver:(EXPromiseResolveBlock)resolve rejecter:(EXPromiseRejectBlock)reject)
51{
52  NSString *uri = options[@"uri"];
53  if (uri == nil) {
54    reject(@"E_FACE_DETECTION_FAILED", @"You must define a URI.", nil);
55    return;
56  }
57
58  NSURL *url = [NSURL URLWithString:uri];
59  NSString *path = [url.path stringByStandardizingPath];
60
61  NSException *exception;
62  id<EXFileSystemInterface> fileSystem = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXFileSystemInterface)];
63  if (!fileSystem || exception) {
64    reject(@"E_MODULE_UNAVAILABLE", @"No file system module", nil);
65    return;
66  }
67
68  if (!([fileSystem permissionsForURI:url] & EXFileSystemPermissionRead)) {
69    reject(@"E_FILESYSTEM_PERMISSIONS", [NSString stringWithFormat:@"File '%@' isn't readable.", uri], nil);
70    return;
71  }
72
73  @try {
74    UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
75    CIImage *ciImage = image.CIImage;
76    if(!ciImage) {
77      ciImage = [CIImage imageWithCGImage:image.CGImage];
78    }
79    ciImage = [ciImage imageByApplyingOrientation:[EXFaceDetectorUtils toCGImageOrientation:image.imageOrientation]];
80    CIContext *context = [CIContext contextWithOptions:@{}];
81    UIImage *temporaryImage = [UIImage imageWithCIImage:ciImage];
82    CGRect tempImageRect = CGRectMake(0, 0, temporaryImage.size.width, temporaryImage.size.height);
83    CGImageRef cgImage = [context createCGImage:ciImage fromRect:tempImageRect];
84
85    UIImage *finalImage = [UIImage imageWithCGImage:cgImage];
86    EXFaceDetector* detector = [[EXFaceDetector alloc] initWithOptions: [EXFaceDetectorUtils mapOptions:options]];
87    [detector detectFromImage:finalImage completionListener:^(NSArray<MLKFace *> * _Nullable faces, NSError * _Nullable error) {
88      NSMutableArray<NSDictionary*>* reportableFaces = [NSMutableArray new];
89
90      if(faces.count > 0) {
91        EXFaceEncoder *encoder = [[EXFaceEncoder alloc] init];
92        for(MLKFace* face in faces)
93      {
94          [reportableFaces addObject:[encoder encode:face]];
95        }
96      }
97
98      CGImageRelease(cgImage);
99      if (error != nil) {
100        reject(@"E_FACE_DETECTION_FAILED", [exception description], nil);
101      } else {
102        resolve(@{
103                  @"faces" : reportableFaces,
104                  @"image" : @{
105                      @"uri" : options[@"uri"],
106                      @"width" : @(image.size.width),
107                      @"height" : @(image.size.height),
108                      @"orientation" : @([EXFaceDetectorModule exifOrientationFor:image.imageOrientation])
109                      }
110                  });
111      }}];
112  } @catch (NSException *exception) {
113    reject(@"E_FACE_DETECTION_FAILED", [exception description], nil);
114  }
115}
116
117# pragma mark: - Utility methods
118
119// https://gist.github.com/steipete/4666527
120+ (int)exifOrientationFor:(UIImageOrientation)orientation
121{
122  switch (orientation) {
123    case UIImageOrientationUp:
124      return 1;
125    case UIImageOrientationDown:
126      return 3;
127    case UIImageOrientationLeft:
128      return 8;
129    case UIImageOrientationRight:
130      return 6;
131    case UIImageOrientationUpMirrored:
132      return 2;
133    case UIImageOrientationDownMirrored:
134      return 4;
135    case UIImageOrientationLeftMirrored:
136      return 5;
137    case UIImageOrientationRightMirrored:
138      return 7;
139  }
140}
141
142@end
143