react-native-camera/ios/FaceDetector/RNFaceDetectorModule.m
2018-01-26 19:12:01 -02:00

196 lines
6.4 KiB
Objective-C

//
// RNFaceDetectorModule.m
// RCTCamera
//
// Created by Joao Guilherme Daros Fidelis on 21/01/18.
//
#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<GMVFaceFeature *> *faces = [detector featuresInImage:image options:detectionOptions];
RNFaceEncoder *faceEncoder = [[RNFaceEncoder alloc] init];
NSMutableArray<NSDictionary *> *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