mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
276 lines
8.6 KiB
Objective-C
276 lines
8.6 KiB
Objective-C
#import "TGPaintFaceDetector.h"
|
|
#import <LegacyComponents/TGPhotoEditorUtils.h>
|
|
#import <LegacyComponents/TGPaintUtils.h>
|
|
#import <ImageIO/ImageIO.h>
|
|
|
|
#import "TGMediaEditingContext.h"
|
|
#import "UIImage+TG.h"
|
|
|
|
@interface TGPaintFace ()
|
|
|
|
+ (instancetype)faceWithBounds:(CGRect)bounds angle:(CGFloat)angle leftEye:(TGPaintFaceEye *)leftEye rightEye:(TGPaintFaceEye *)rightEye mouth:(TGPaintFaceMouth *)mouth;
|
|
|
|
@end
|
|
|
|
|
|
@interface TGPaintFaceEye ()
|
|
|
|
+ (instancetype)eyeWithPosition:(CGPoint)position closed:(bool)closed;
|
|
|
|
@end
|
|
|
|
|
|
@interface TGPaintFaceMouth ()
|
|
|
|
+ (instancetype)mouthWithPosition:(CGPoint)position smiling:(bool)smiling;
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGPaintFaceDetector
|
|
|
|
+ (SSignal *)detectFacesInItem:(id<TGMediaEditableItem>)item editingContext:(TGMediaEditingContext *)editingContext
|
|
{
|
|
CGSize originalSize = item.originalSize;
|
|
|
|
SSignal *cachedFaces = [editingContext facesForItem:item];
|
|
SSignal *cachedSignal = [cachedFaces mapToSignal:^SSignal *(id result)
|
|
{
|
|
if (result == nil)
|
|
return [SSignal fail:nil];
|
|
return [SSignal single:result];
|
|
}];
|
|
|
|
SSignal *imageSignal = [item screenImageSignal:0];
|
|
SSignal *detectSignal = [[[imageSignal filter:^bool(UIImage *image)
|
|
{
|
|
if (![image isKindOfClass:[UIImage class]])
|
|
return false;
|
|
|
|
if (image.degraded)
|
|
return false;
|
|
|
|
return true;
|
|
}] take:1] mapToSignal:^SSignal *(UIImage *image) {
|
|
return [[TGPaintFaceDetector detectFacesInImage:image originalSize:originalSize] startOn:[SQueue concurrentDefaultQueue]];
|
|
}];
|
|
|
|
return [[[cachedSignal catch:^SSignal *(__unused id error)
|
|
{
|
|
return detectSignal;
|
|
}] deliverOn:[SQueue mainQueue]] onNext:^(NSArray *next)
|
|
{
|
|
[editingContext setFaces:next forItem:item];
|
|
}];
|
|
}
|
|
|
|
+ (SSignal *)detectFacesInImage:(UIImage *)image originalSize:(CGSize)originalSize
|
|
{
|
|
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
|
|
{
|
|
NSDictionary *options = @
|
|
{
|
|
CIDetectorAccuracy: CIDetectorAccuracyHigh,
|
|
CIDetectorMinFeatureSize : @(0.175)
|
|
};
|
|
|
|
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeFace context:nil options:options];
|
|
|
|
CIImage *ciImage = [[CIImage imageWithCGImage:image.CGImage] imageByApplyingOrientation:[self tiffOrientationFromImageOrientation:image.imageOrientation]];
|
|
|
|
NSMutableArray *result = [[NSMutableArray alloc] init];
|
|
NSArray *features = [detector featuresInImage:ciImage];
|
|
for (CIFaceFeature *feature in features)
|
|
{
|
|
if (![feature isKindOfClass:[CIFaceFeature class]])
|
|
continue;
|
|
|
|
TGPaintFace *face = [self _paintFaceForFaceFeature:feature initialSize:image.size targetSize:originalSize];
|
|
if (face != nil)
|
|
[result addObject:face];
|
|
}
|
|
|
|
[subscriber putNext:result];
|
|
[subscriber putCompletion];
|
|
|
|
return nil;
|
|
}];
|
|
}
|
|
|
|
+ (int)tiffOrientationFromImageOrientation:(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;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
+ (TGPaintFace *)_paintFaceForFaceFeature:(CIFaceFeature *)faceFeature initialSize:(CGSize)initialSize targetSize:(CGSize)targetSize
|
|
{
|
|
if (faceFeature == nil)
|
|
return nil;
|
|
|
|
if (!faceFeature.hasLeftEyePosition || !faceFeature.hasRightEyePosition)
|
|
return nil;
|
|
|
|
TGPaintFaceEye *leftEye = [TGPaintFaceEye eyeWithPosition:[self _transposedPoint:faceFeature.leftEyePosition initialSize:initialSize targetSize:targetSize] closed:faceFeature.leftEyeClosed];
|
|
TGPaintFaceEye *rightEye = [TGPaintFaceEye eyeWithPosition:[self _transposedPoint:faceFeature.rightEyePosition initialSize:initialSize targetSize:targetSize] closed:faceFeature.rightEyeClosed];
|
|
|
|
TGPaintFaceMouth *mouth = nil;
|
|
|
|
if (faceFeature.hasMouthPosition)
|
|
mouth = [TGPaintFaceMouth mouthWithPosition:[self _transposedPoint:faceFeature.mouthPosition initialSize:initialSize targetSize:targetSize] smiling:faceFeature.hasSmile];
|
|
|
|
return [TGPaintFace faceWithBounds:[self _transposedRect:faceFeature.bounds initialSize:initialSize targetSize:targetSize] angle:[self _transposedAngle:TGDegreesToRadians(faceFeature.faceAngle)] leftEye:leftEye rightEye:rightEye mouth:mouth];
|
|
}
|
|
|
|
+ (CGFloat)_transposedAngle:(CGFloat)angle
|
|
{
|
|
return angle;
|
|
}
|
|
|
|
+ (CGRect)_transposedRect:(CGRect)rect initialSize:(CGSize)initialSize targetSize:(CGSize)targetSize
|
|
{
|
|
return CGRectMake(targetSize.width * rect.origin.x / initialSize.width, targetSize.height * (initialSize.height - rect.origin.y - rect.size.height) / initialSize.height, targetSize.width * rect.size.width / initialSize.width, targetSize.height * rect.size.height / initialSize.height);
|
|
}
|
|
|
|
+ (CGPoint)_transposedPoint:(CGPoint)point initialSize:(CGSize)initialSize targetSize:(CGSize)targetSize
|
|
{
|
|
return CGPointMake(targetSize.width * point.x / initialSize.width, targetSize.height - targetSize.height * point.y / initialSize.height);
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGPaintFace
|
|
|
|
+ (instancetype)faceWithBounds:(CGRect)bounds angle:(CGFloat)angle leftEye:(TGPaintFaceEye *)leftEye rightEye:(TGPaintFaceEye *)rightEye mouth:(TGPaintFaceMouth *)mouth
|
|
{
|
|
TGPaintFace *face = [[TGPaintFace alloc] init];
|
|
arc4random_buf(&face->_uuid, sizeof(NSInteger));
|
|
face->_bounds = bounds;
|
|
face->_angle = angle;
|
|
face->_leftEye = leftEye;
|
|
face->_rightEye = rightEye;
|
|
face->_mouth = mouth;
|
|
return face;
|
|
}
|
|
|
|
- (CGPoint)foreheadPoint
|
|
{
|
|
CGFloat halfFace = _bounds.size.height / 2.0f;
|
|
CGPoint point = TGPaintCenterOfRect(_bounds);
|
|
|
|
return CGPointMake(point.x + halfFace * cos(_angle - M_PI_2), point.y + halfFace * sin(_angle - M_PI_2));
|
|
}
|
|
|
|
- (CGPoint)eyesCenterPointAndDistance:(CGFloat *)distance
|
|
{
|
|
CGPoint point = CGPointMake(0.5f * _leftEye.position.x + 0.5f * _rightEye.position.x, 0.5f * _leftEye.position.y + 0.5f * _rightEye.position.y);
|
|
CGFloat dist = sqrt(pow(_rightEye.position.x - _leftEye.position.x, 2) + pow(_rightEye.position.y - _leftEye.position.y, 2));
|
|
|
|
if (distance != NULL)
|
|
*distance = dist;
|
|
|
|
return point;
|
|
}
|
|
|
|
- (CGFloat)eyesAngle
|
|
{
|
|
return atan2(_rightEye.position.y - _leftEye.position.y, _rightEye.position.x - _leftEye.position.x);
|
|
}
|
|
|
|
- (CGPoint)mouthPoint
|
|
{
|
|
return _mouth.position;
|
|
}
|
|
|
|
- (CGPoint)chinPoint
|
|
{
|
|
CGFloat halfFace = _bounds.size.height / 2.0f;
|
|
CGPoint point = TGPaintCenterOfRect(_bounds);
|
|
|
|
return CGPointMake(point.x + halfFace * cos(_angle + M_PI_2), point.y + halfFace * sin(_angle + M_PI_2));
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGPaintFaceFeature
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGPaintFaceEye
|
|
|
|
+ (instancetype)eyeWithPosition:(CGPoint)position closed:(bool)closed
|
|
{
|
|
TGPaintFaceEye *eye = [[TGPaintFaceEye alloc] init];
|
|
eye->_position = position;
|
|
eye->_closed = closed;
|
|
return eye;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGPaintFaceMouth
|
|
|
|
+ (instancetype)mouthWithPosition:(CGPoint)position smiling:(bool)smiling
|
|
{
|
|
TGPaintFaceMouth *mouth = [[TGPaintFaceMouth alloc] init];
|
|
mouth->_position = position;
|
|
mouth->_smiling = smiling;
|
|
return mouth;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation TGPaintFaceUtils
|
|
|
|
+ (CGFloat)transposeWidth:(CGFloat)width paintingSize:(CGSize)paintingSize originalSize:(CGSize)originalSize
|
|
{
|
|
return width * paintingSize.width / originalSize.width;
|
|
}
|
|
|
|
+ (CGPoint)transposePoint:(CGPoint)point paintingSize:(CGSize)paintingSize originalSize:(CGSize)originalSize
|
|
{
|
|
return CGPointMake(point.x * paintingSize.width / originalSize.width, point.y * paintingSize.height / originalSize.height);
|
|
}
|
|
|
|
+ (CGRect)transposeRect:(CGRect)rect paintingSize:(CGSize)paintingSize originalSize:(CGSize)originalSize
|
|
{
|
|
CGPoint origin = [self transposePoint:rect.origin paintingSize:paintingSize originalSize:originalSize];
|
|
CGSize size = CGSizeMake(rect.size.width * paintingSize.width / originalSize.width, rect.size.height * paintingSize.height / originalSize.height);
|
|
|
|
return (CGRect){ origin, size };
|
|
}
|
|
|
|
@end
|