2014 snapchat source code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
9.3 KiB

  1. //
  2. // SCManagedCaptureFaceDetectionAdjustingPOIResource.m
  3. // Snapchat
  4. //
  5. // Created by Jiyang Zhu on 3/7/18.
  6. // Copyright © 2018 Snapchat, Inc. All rights reserved.
  7. //
  8. #import "SCManagedCaptureFaceDetectionAdjustingPOIResource.h"
  9. #import <SCFoundation/SCLog.h>
  10. #import <SCFoundation/SCTrace.h>
  11. #import <SCFoundation/SCTraceODPCompatible.h>
  12. @implementation SCManagedCaptureFaceDetectionAdjustingPOIResource {
  13. CGPoint _defaultPointOfInterest;
  14. }
  15. #pragma mark - Public Methods
  16. - (instancetype)initWithDefaultPointOfInterest:(CGPoint)pointOfInterest
  17. shouldTargetOnFaceAutomatically:(BOOL)shouldTargetOnFaceAutomatically
  18. {
  19. if (self = [super init]) {
  20. _pointOfInterest = pointOfInterest;
  21. _defaultPointOfInterest = pointOfInterest;
  22. _shouldTargetOnFaceAutomatically = shouldTargetOnFaceAutomatically;
  23. }
  24. return self;
  25. }
  26. - (void)reset
  27. {
  28. SCTraceODPCompatibleStart(2);
  29. self.adjustingPOIMode = SCManagedCaptureFaceDetectionAdjustingPOIModeNone;
  30. self.targetingFaceID = nil;
  31. self.targetingFaceBounds = CGRectZero;
  32. self.faceBoundsByFaceID = nil;
  33. self.pointOfInterest = _defaultPointOfInterest;
  34. }
  35. - (CGPoint)updateWithNewProposedPointOfInterest:(CGPoint)proposedPoint fromUser:(BOOL)fromUser
  36. {
  37. SCTraceODPCompatibleStart(2);
  38. if (fromUser) {
  39. NSNumber *faceID =
  40. [self _getFaceIDOfFaceBoundsContainingPoint:proposedPoint fromFaceBounds:self.faceBoundsByFaceID];
  41. if (faceID && [faceID integerValue] >= 0) {
  42. CGPoint point = [self _getPointOfInterestWithFaceID:faceID fromFaceBounds:self.faceBoundsByFaceID];
  43. if ([self _isPointOfInterestValid:point]) {
  44. [self _setPointOfInterest:point
  45. targetingFaceID:faceID
  46. adjustingPOIMode:SCManagedCaptureFaceDetectionAdjustingPOIModeFixedOnPointWithFace];
  47. } else {
  48. [self _setPointOfInterest:proposedPoint
  49. targetingFaceID:nil
  50. adjustingPOIMode:SCManagedCaptureFaceDetectionAdjustingPOIModeFixedOnPointWithoutFace];
  51. }
  52. } else {
  53. [self _setPointOfInterest:proposedPoint
  54. targetingFaceID:nil
  55. adjustingPOIMode:SCManagedCaptureFaceDetectionAdjustingPOIModeFixedOnPointWithoutFace];
  56. }
  57. } else {
  58. [self _setPointOfInterest:proposedPoint
  59. targetingFaceID:nil
  60. adjustingPOIMode:SCManagedCaptureFaceDetectionAdjustingPOIModeNone];
  61. }
  62. return self.pointOfInterest;
  63. }
  64. - (CGPoint)updateWithNewDetectedFaceBounds:(NSDictionary<NSNumber *, NSValue *> *)faceBoundsByFaceID
  65. {
  66. SCTraceODPCompatibleStart(2);
  67. self.faceBoundsByFaceID = faceBoundsByFaceID;
  68. switch (self.adjustingPOIMode) {
  69. case SCManagedCaptureFaceDetectionAdjustingPOIModeNone: {
  70. if (self.shouldTargetOnFaceAutomatically) {
  71. [self _focusOnPreferredFaceInFaceBounds:self.faceBoundsByFaceID];
  72. }
  73. } break;
  74. case SCManagedCaptureFaceDetectionAdjustingPOIModeFixedOnPointWithFace: {
  75. BOOL isFocusingOnCurrentTargetingFaceSuccess =
  76. [self _focusOnFaceWithTargetFaceID:self.targetingFaceID inFaceBounds:self.faceBoundsByFaceID];
  77. if (!isFocusingOnCurrentTargetingFaceSuccess && self.shouldTargetOnFaceAutomatically) {
  78. // If the targeted face has disappeared, and shouldTargetOnFaceAutomatically is YES, automatically target on
  79. // the next preferred face.
  80. [self _focusOnPreferredFaceInFaceBounds:self.faceBoundsByFaceID];
  81. }
  82. } break;
  83. case SCManagedCaptureFaceDetectionAdjustingPOIModeFixedOnPointWithoutFace:
  84. // The point of interest should be fixed at a non-face point where user tapped before.
  85. break;
  86. }
  87. return self.pointOfInterest;
  88. }
  89. #pragma mark - Internal Methods
  90. - (BOOL)_focusOnPreferredFaceInFaceBounds:(NSDictionary<NSNumber *, NSValue *> *)faceBoundsByFaceID
  91. {
  92. SCTraceODPCompatibleStart(2);
  93. NSNumber *preferredFaceID = [self _getPreferredFaceIDFromFaceBounds:faceBoundsByFaceID];
  94. return [self _focusOnFaceWithTargetFaceID:preferredFaceID inFaceBounds:faceBoundsByFaceID];
  95. }
  96. - (BOOL)_focusOnFaceWithTargetFaceID:(NSNumber *)preferredFaceID
  97. inFaceBounds:(NSDictionary<NSNumber *, NSValue *> *)faceBoundsByFaceID
  98. {
  99. SCTraceODPCompatibleStart(2);
  100. SC_GUARD_ELSE_RETURN_VALUE(preferredFaceID, NO);
  101. NSValue *faceBoundsValue = [faceBoundsByFaceID objectForKey:preferredFaceID];
  102. if (faceBoundsValue) {
  103. CGRect faceBounds = [faceBoundsValue CGRectValue];
  104. CGPoint proposedPoint = CGPointMake(CGRectGetMidX(faceBounds), CGRectGetMidY(faceBounds));
  105. if ([self _isPointOfInterestValid:proposedPoint]) {
  106. if ([self _shouldChangeToNewPoint:proposedPoint withNewFaceID:preferredFaceID newFaceBounds:faceBounds]) {
  107. [self _setPointOfInterest:proposedPoint
  108. targetingFaceID:preferredFaceID
  109. adjustingPOIMode:SCManagedCaptureFaceDetectionAdjustingPOIModeFixedOnPointWithFace];
  110. }
  111. return YES;
  112. }
  113. }
  114. [self reset];
  115. return NO;
  116. }
  117. - (void)_setPointOfInterest:(CGPoint)pointOfInterest
  118. targetingFaceID:(NSNumber *)targetingFaceID
  119. adjustingPOIMode:(SCManagedCaptureFaceDetectionAdjustingPOIMode)adjustingPOIMode
  120. {
  121. SCTraceODPCompatibleStart(2);
  122. self.pointOfInterest = pointOfInterest;
  123. self.targetingFaceID = targetingFaceID;
  124. if (targetingFaceID) { // If targetingFaceID exists, record the current face bounds.
  125. self.targetingFaceBounds = [[self.faceBoundsByFaceID objectForKey:targetingFaceID] CGRectValue];
  126. } else { // Otherwise, reset targetingFaceBounds to zero.
  127. self.targetingFaceBounds = CGRectZero;
  128. }
  129. self.adjustingPOIMode = adjustingPOIMode;
  130. }
  131. - (BOOL)_isPointOfInterestValid:(CGPoint)pointOfInterest
  132. {
  133. return (pointOfInterest.x >= 0 && pointOfInterest.x <= 1 && pointOfInterest.y >= 0 && pointOfInterest.y <= 1);
  134. }
  135. - (NSNumber *)_getPreferredFaceIDFromFaceBounds:(NSDictionary<NSNumber *, NSValue *> *)faceBoundsByFaceID
  136. {
  137. SCTraceODPCompatibleStart(2);
  138. SC_GUARD_ELSE_RETURN_VALUE(faceBoundsByFaceID.count > 0, nil);
  139. // Find out the bounds with the max area.
  140. __block NSNumber *preferredFaceID = nil;
  141. __block CGFloat maxArea = 0;
  142. [faceBoundsByFaceID
  143. enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSValue *_Nonnull obj, BOOL *_Nonnull stop) {
  144. CGRect faceBounds = [obj CGRectValue];
  145. CGFloat area = CGRectGetWidth(faceBounds) * CGRectGetHeight(faceBounds);
  146. if (area > maxArea) {
  147. preferredFaceID = key;
  148. maxArea = area;
  149. }
  150. }];
  151. return preferredFaceID;
  152. }
  153. - (CGPoint)_getPointOfInterestWithFaceID:(NSNumber *)faceID
  154. fromFaceBounds:(NSDictionary<NSNumber *, NSValue *> *)faceBoundsByFaceID
  155. {
  156. SCTraceODPCompatibleStart(2);
  157. NSValue *faceBoundsValue = [faceBoundsByFaceID objectForKey:faceID];
  158. if (faceBoundsValue) {
  159. CGRect faceBounds = [faceBoundsValue CGRectValue];
  160. CGPoint point = CGPointMake(CGRectGetMidX(faceBounds), CGRectGetMidY(faceBounds));
  161. return point;
  162. } else {
  163. return CGPointMake(-1, -1); // An invalid point.
  164. }
  165. }
  166. /**
  167. Setting a new focus/exposure point needs high CPU usage, so we only set a new POI when we have to. This method is to
  168. return whether setting this new point if necessary.
  169. If not, there is no need to change the POI.
  170. */
  171. - (BOOL)_shouldChangeToNewPoint:(CGPoint)newPoint
  172. withNewFaceID:(NSNumber *)newFaceID
  173. newFaceBounds:(CGRect)newFaceBounds
  174. {
  175. SCTraceODPCompatibleStart(2);
  176. BOOL shouldChange = NO;
  177. if (!newFaceID || !self.targetingFaceID ||
  178. ![newFaceID isEqualToNumber:self.targetingFaceID]) { // Return YES if it is a new face.
  179. shouldChange = YES;
  180. } else if (CGRectEqualToRect(self.targetingFaceBounds, CGRectZero) ||
  181. !CGRectContainsPoint(self.targetingFaceBounds,
  182. newPoint)) { // Return YES if the new point if out of the current face bounds.
  183. shouldChange = YES;
  184. } else {
  185. CGFloat currentBoundsArea =
  186. CGRectGetWidth(self.targetingFaceBounds) * CGRectGetHeight(self.targetingFaceBounds);
  187. CGFloat newBoundsArea = CGRectGetWidth(newFaceBounds) * CGRectGetHeight(newFaceBounds);
  188. if (newBoundsArea >= currentBoundsArea * 1.2 ||
  189. newBoundsArea <=
  190. currentBoundsArea *
  191. 0.8) { // Return YES if the area of new bounds if over 20% more or 20% less than the current one.
  192. shouldChange = YES;
  193. }
  194. }
  195. return shouldChange;
  196. }
  197. - (NSNumber *)_getFaceIDOfFaceBoundsContainingPoint:(CGPoint)point
  198. fromFaceBounds:(NSDictionary<NSNumber *, NSValue *> *)faceBoundsByFaceID
  199. {
  200. SC_GUARD_ELSE_RETURN_VALUE(faceBoundsByFaceID.count > 0, nil);
  201. __block NSNumber *faceID = nil;
  202. [faceBoundsByFaceID
  203. enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull key, NSValue *_Nonnull obj, BOOL *_Nonnull stop) {
  204. CGRect faceBounds = [obj CGRectValue];
  205. if (CGRectContainsPoint(faceBounds, point)) {
  206. faceID = key;
  207. *stop = YES;
  208. }
  209. }];
  210. return faceID;
  211. }
  212. @end