// // RNFaceDetectorModule.m // RCTCamera // // Created by Joao Guilherme Daros Fidelis on 21/01/18. // #if __has_include() #import "RNFaceDetectorModule.h" #import "RNFaceEncoder.h" #import "RNFileSystem.h" #import "RNFaceDetectorUtils.h" static const NSString *kModeOptionName = @"mode"; static const NSString *kDetectLandmarksOptionName = @"detectLandmarks"; static const NSString *kRunClassificationsOptionName = @"runClassifications"; @implementation RNFaceDetectorModule static NSFileManager *fileManager = nil; static NSDictionary *defaultDetectorOptions = nil; - (instancetype)init { self = [super init]; if (self) { fileManager = [NSFileManager defaultManager]; } return self; } RCT_EXPORT_MODULE(RNFaceDetector); @synthesize bridge = _bridge; - (void)setBridge:(RCTBridge *)bridge { _bridge = bridge; } + (BOOL)requiresMainQueueSetup { return NO; } - (NSDictionary *)constantsToExport { return [RNFaceDetectorUtils constantsToExport]; } RCT_EXPORT_METHOD(detectFaces:(nonnull NSDictionary *)options resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { NSString *uri = options[@"uri"]; if (uri == nil) { reject(@"E_FACE_DETECTION_FAILED", @"You must define a URI.", nil); return; } NSURL *url = [NSURL URLWithString:uri]; NSString *path = [url.path stringByStandardizingPath]; // if (!([self.bridge.scopedModules.fileSystem permissionsForURI:url] & EXFileSystemPermissionRead)) { // reject(@"E_FILESYSTEM_PERMISSIONS", [NSString stringWithFormat:@"File '%@' isn't readable.", uri], nil); // return; // } @try { GMVDetector *detector = [[self class] detectorForOptions:options]; if (![fileManager fileExistsAtPath:path]) { reject(@"E_FACE_DETECTION_FAILED", [NSString stringWithFormat:@"The file does not exist. Given path: `%@`.", path], nil); return; } UIImage *image = [[UIImage alloc] initWithContentsOfFile:path]; NSDictionary *detectionOptions = [[self class] detectionOptionsForImage:image]; NSArray *faces = [detector featuresInImage:image options:detectionOptions]; RNFaceEncoder *faceEncoder = [[RNFaceEncoder alloc] init]; NSMutableArray *encodedFaces = [NSMutableArray arrayWithCapacity:[faces count]]; [faces enumerateObjectsUsingBlock:^(GMVFaceFeature * _Nonnull face, NSUInteger _idx, BOOL * _Nonnull _stop) { [encodedFaces addObject:[faceEncoder encode:face]]; }]; resolve(@{ @"faces" : encodedFaces, @"image" : @{ @"uri" : options[@"uri"], @"width" : @(image.size.width), @"height" : @(image.size.height), @"orientation" : @([RNFaceDetectorModule exifOrientationFor:image.imageOrientation]) } }); } @catch (NSException *exception) { reject(@"E_FACE_DETECTION_FAILED", [exception description], nil); } } + (GMVDetector *)detectorForOptions:(NSDictionary *)options { NSMutableDictionary *parsedOptions = [[NSMutableDictionary alloc] initWithDictionary:[self getDefaultDetectorOptions]]; if (options[kDetectLandmarksOptionName]) { [parsedOptions setObject:options[kDetectLandmarksOptionName] forKey:GMVDetectorFaceLandmarkType]; } if (options[kModeOptionName]) { [parsedOptions setObject:options[kModeOptionName] forKey:GMVDetectorFaceMode]; } if (options[kRunClassificationsOptionName]) { [parsedOptions setObject:options[kRunClassificationsOptionName] forKey:GMVDetectorFaceClassificationType]; } return [GMVDetector detectorOfType:GMVDetectorTypeFace options:parsedOptions]; } # pragma mark: - Detector default options getter and initializer + (NSDictionary *)getDefaultDetectorOptions { if (defaultDetectorOptions == nil) { [self initDefaultDetectorOptions]; } return defaultDetectorOptions; } + (void)initDefaultDetectorOptions { defaultDetectorOptions = @{ GMVDetectorFaceMode : @(GMVDetectorFaceAccurateMode), GMVDetectorFaceLandmarkType : @(GMVDetectorFaceLandmarkAll), GMVDetectorFaceClassificationType : @(GMVDetectorFaceClassificationAll) }; } # pragma mark: - Utility methods + (NSDictionary *)detectionOptionsForImage:(UIImage *)image { return @{ GMVDetectorImageOrientation : @([[self class] gmvImageOrientationFor:image.imageOrientation]), }; } // As the documentation (http://cocoadocs.org/docsets/GoogleMobileVision/1.0.2/Constants/GMVImageOrientation.html) suggests // the value of GMVImageOrientation is the same as the value defined by EXIF specifications, so we can adapt // https://gist.github.com/steipete/4666527 to our needs. + (GMVImageOrientation)gmvImageOrientationFor:(UIImageOrientation)orientation { switch (orientation) { case UIImageOrientationUp: return GMVImageOrientationTopLeft; case UIImageOrientationDown: return GMVImageOrientationBottomRight; case UIImageOrientationLeft: return GMVImageOrientationLeftBottom; case UIImageOrientationRight: return GMVImageOrientationRightTop; case UIImageOrientationUpMirrored: return GMVImageOrientationTopRight; case UIImageOrientationDownMirrored: return GMVImageOrientationBottomLeft; case UIImageOrientationLeftMirrored: return GMVImageOrientationLeftTop; case UIImageOrientationRightMirrored: return GMVImageOrientationRightBottom; } } // https://gist.github.com/steipete/4666527 + (int)exifOrientationFor:(UIImageOrientation)orientation { switch (orientation) { case UIImageOrientationUp: return 1; case UIImageOrientationDown: return 3; case UIImageOrientationLeft: return 8; case UIImageOrientationRight: return 6; case UIImageOrientationUpMirrored: return 2; case UIImageOrientationDownMirrored: return 4; case UIImageOrientationLeftMirrored: return 5; case UIImageOrientationRightMirrored: return 7; } } @end #endif