2020-05-28 06:00:36 +03:00

552 lines
18 KiB
Objective-C

#import "PGPhotoEditor.h"
#import <SSignalKit/SSignalKit.h>
#import <LegacyComponents/TGMemoryImageCache.h>
#import "LegacyComponentsInternal.h"
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import "TGPhotoEditorPreviewView.h"
#import "PGPhotoEditorView.h"
#import "PGPhotoEditorPicture.h"
#import "GPUImageTextureInput.h"
#import "GPUImageCropFilter.h"
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGVideoEditAdjustments.h>
#import <LegacyComponents/TGPaintingData.h>
#import "PGVideoMovie.h"
#import "PGPhotoToolComposer.h"
#import "PGEnhanceTool.h"
#import "PGExposureTool.h"
#import "PGContrastTool.h"
#import "PGWarmthTool.h"
#import "PGSaturationTool.h"
#import "PGHighlightsTool.h"
#import "PGShadowsTool.h"
#import "PGVignetteTool.h"
#import "PGGrainTool.h"
#import "PGBlurTool.h"
#import "PGSharpenTool.h"
#import "PGFadeTool.h"
#import "PGTintTool.h"
#import "PGCurvesTool.h"
#import "PGPhotoHistogramGenerator.h"
@interface PGPhotoEditor ()
{
PGPhotoToolComposer *_toolComposer;
id<TGMediaEditAdjustments> _initialAdjustments;
GPUImageOutput *_currentInput;
GPUImageCropFilter *_cropFilter;
GPUImageRotationMode _rotationMode;
NSArray *_currentProcessChain;
GPUImageOutput <GPUImageInput> *_finalFilter;
PGPhotoHistogram *_currentHistogram;
PGPhotoHistogramGenerator *_histogramGenerator;
UIImageOrientation _imageCropOrientation;
CGRect _imageCropRect;
CGFloat _imageCropRotation;
bool _imageCropMirrored;
SPipe *_histogramPipe;
SQueue *_queue;
SQueue *_videoQueue;
bool _playing;
bool _processing;
bool _needsReprocessing;
bool _fullSize;
}
@end
@implementation PGPhotoEditor
- (instancetype)initWithOriginalSize:(CGSize)originalSize adjustments:(id<TGMediaEditAdjustments>)adjustments forVideo:(bool)forVideo enableStickers:(bool)enableStickers
{
self = [super init];
if (self != nil)
{
_queue = [[SQueue alloc] init];
_videoQueue = [[SQueue alloc] init];
_forVideo = forVideo;
_enableStickers = enableStickers;
_originalSize = originalSize;
_cropRect = CGRectMake(0.0f, 0.0f, _originalSize.width, _originalSize.height);
_paintingData = adjustments.paintingData;
_tools = [self toolsInit];
_toolComposer = [[PGPhotoToolComposer alloc] init];
[_toolComposer addPhotoTools:_tools];
[_toolComposer compose];
_histogramPipe = [[SPipe alloc] init];
if (!forVideo) {
__weak PGPhotoEditor *weakSelf = self;
_histogramGenerator = [[PGPhotoHistogramGenerator alloc] init];
_histogramGenerator.histogramReady = ^(PGPhotoHistogram *histogram)
{
__strong PGPhotoEditor *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_currentHistogram = histogram;
strongSelf->_histogramPipe.sink(histogram);
};
}
[self importAdjustments:adjustments];
}
return self;
}
- (void)dealloc
{
if ([_currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)_currentInput cancelProcessing];
}
TGDispatchAfter(1.5f, dispatch_get_main_queue(), ^
{
[[GPUImageContext sharedFramebufferCache] purgeAllUnassignedFramebuffers];
});
}
- (void)cleanup
{
[[GPUImageContext sharedFramebufferCache] purgeAllUnassignedFramebuffers];
}
- (NSArray *)toolsInit
{
NSMutableArray *tools = [NSMutableArray array];
for (Class toolClass in [PGPhotoEditor availableTools])
{
PGPhotoTool *toolInstance = [[toolClass alloc] init];
if (!_forVideo || toolInstance.isAvialableForVideo) {
[tools addObject:toolInstance];
}
}
return tools;
}
- (void)setImage:(UIImage *)image forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored fullSize:(bool)fullSize
{
[_toolComposer invalidate];
_currentProcessChain = nil;
_imageCropRect = cropRect;
_imageCropRotation = cropRotation;
_imageCropOrientation = cropOrientation;
_imageCropMirrored = cropMirrored;
[_currentInput removeAllTargets];
_currentInput = [[PGPhotoEditorPicture alloc] initWithImage:image];
_histogramGenerator.imageSize = image.size;
_fullSize = fullSize;
}
- (void)setPlayerItem:(AVPlayerItem *)playerItem forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored {
[_toolComposer invalidate];
_currentProcessChain = nil;
[_currentInput removeAllTargets];
PGVideoMovie *movie = [[PGVideoMovie alloc] initWithPlayerItem:playerItem];
_currentInput = movie;
bool hasCropping = !CGPointEqualToPoint(cropRect.origin, CGPointZero) || (!CGSizeEqualToSize(cropRect.size, CGSizeZero) && !CGSizeEqualToSize(cropRect.size, _originalSize));
_rotationMode = kGPUImageNoRotation;
if (cropOrientation != UIImageOrientationUp || cropMirrored || hasCropping) {
CGRect normalizedCropRect = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
if (hasCropping) {
normalizedCropRect = CGRectMake(cropRect.origin.x / _originalSize.width, cropRect.origin.y / _originalSize.height, cropRect.size.width / _originalSize.width, cropRect.size.height / _originalSize.height);
}
_cropFilter = [[GPUImageCropFilter alloc] initWithCropRegion:normalizedCropRect];
if (cropOrientation != UIImageOrientationUp || cropMirrored) {
switch (cropOrientation) {
case UIImageOrientationLeft:
_rotationMode = kGPUImageRotateLeft;
break;
case UIImageOrientationRight:
_rotationMode = cropMirrored ? kGPUImageRotateRightFlipHorizontal : kGPUImageRotateRight;
break;
case UIImageOrientationDown:
_rotationMode = kGPUImageRotate180;
break;
case UIImageOrientationUp:
if (cropMirrored)
_rotationMode = kGPUImageFlipHorizonal;
break;
default:
break;
}
}
}
_fullSize = true;
}
- (void)setCIImage:(CIImage *)ciImage {
[_toolComposer invalidate];
_currentProcessChain = nil;
[_currentInput removeAllTargets];
if ([_currentInput isKindOfClass:[GPUImageTextureInput class]]) {
[(GPUImageTextureInput *)_currentInput setCIImage:ciImage];
} else {
GPUImageTextureInput *input = [[GPUImageTextureInput alloc] initWithCIImage:ciImage];
_currentInput = input;
}
_fullSize = true;
}
#pragma mark - Properties
- (CGSize)rotatedCropSize
{
if (_cropOrientation == UIImageOrientationLeft || _cropOrientation == UIImageOrientationRight)
return CGSizeMake(_cropRect.size.height, _cropRect.size.width);
return _cropRect.size;
}
- (bool)hasDefaultCropping
{
if (!_CGRectEqualToRectWithEpsilon(self.cropRect, CGRectMake(0, 0, _originalSize.width, _originalSize.height), 1.0f) || self.cropOrientation != UIImageOrientationUp || ABS(self.cropRotation) > FLT_EPSILON || self.cropMirrored)
{
return false;
}
return true;
}
#pragma mark - Processing
- (bool)readyForProcessing
{
return (_currentInput != nil);
}
- (void)processAnimated:(bool)animated completion:(void (^)(void))completion
{
[self processAnimated:animated capture:false synchronous:false completion:completion];
}
- (void)processAnimated:(bool)animated capture:(bool)capture synchronous:(bool)synchronous completion:(void (^)(void))completion
{
if (self.previewOutput == nil && !self.standalone)
return;
if (self.forVideo) {
[_queue dispatch:^
{
[self updateProcessChain];
GPUImageOutput *currentInput = _currentInput;
if ([currentInput isKindOfClass:[PGVideoMovie class]]) {
if (!_playing) {
_playing = true;
[_videoQueue dispatch:^{
if ([currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)currentInput startProcessing];
}
}];
}
} else if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) {
if (capture)
[_finalFilter useNextFrameForImageCapture];
[(GPUImageTextureInput *)currentInput processTextureWithFrameTime:kCMTimeZero synchronous:synchronous completion:^{
if (completion != nil)
completion();
}];
[_finalFilter commitImageCapture];
}
} synchronous:synchronous];
return;
}
if (iosMajorVersion() < 7)
animated = false;
if (_processing && completion == nil)
{
_needsReprocessing = true;
return;
}
_processing = true;
[_queue dispatch:^
{
[self updateProcessChain];
if (!self.forVideo && capture)
[_finalFilter useNextFrameForImageCapture];
TGPhotoEditorPreviewView *previewOutput = self.previewOutput;
if ([_currentInput isKindOfClass:[PGPhotoEditorPicture class]]) {
PGPhotoEditorPicture *picture = (PGPhotoEditorPicture *)_currentInput;
if (animated)
{
TGDispatchOnMainThread(^
{
[previewOutput prepareTransitionFadeView];
});
}
[picture processSynchronous:true completion:^
{
if (completion != nil)
completion();
_processing = false;
if (animated)
{
TGDispatchOnMainThread(^
{
[previewOutput performTransitionFade];
});
}
if (_needsReprocessing && !synchronous)
{
_needsReprocessing = false;
[self processAnimated:false completion:nil];
}
}];
} else {
}
} synchronous:synchronous];
}
- (void)reprocess {
if ([_currentInput isKindOfClass:[PGVideoMovie class]]) {
[(PGVideoMovie *)_currentInput reprocessCurrent];
}
}
- (void)updateProcessChain {
[GPUImageFramebuffer setMark:self.forVideo];
NSMutableArray *processChain = [NSMutableArray array];
for (PGPhotoTool *tool in _toolComposer.advancedTools)
{
if (!tool.shouldBeSkipped && tool.pass != nil)
[processChain addObject:tool.pass];
}
_toolComposer.imageSize = _cropRect.size;
[processChain addObject:_toolComposer];
TGPhotoEditorPreviewView *previewOutput = self.previewOutput;
if (![_currentProcessChain isEqualToArray:processChain])
{
[_currentInput removeAllTargets];
for (PGPhotoProcessPass *pass in _currentProcessChain)
[pass.filter removeAllTargets];
_currentProcessChain = processChain;
GPUImageOutput <GPUImageInput> *lastFilter = ((PGPhotoProcessPass *)_currentProcessChain.firstObject).filter;
if (_cropFilter != nil) {
[_currentInput addTarget:_cropFilter];
[_cropFilter addTarget:lastFilter];
} else {
[_currentInput addTarget:lastFilter];
}
NSInteger chainLength = _currentProcessChain.count;
if (chainLength > 1)
{
for (NSInteger i = 1; i < chainLength; i++)
{
PGPhotoProcessPass *pass = ((PGPhotoProcessPass *)_currentProcessChain[i]);
GPUImageOutput <GPUImageInput> *filter = pass.filter;
[lastFilter addTarget:filter];
lastFilter = filter;
}
}
_finalFilter = lastFilter;
if (previewOutput != nil) {
[_finalFilter addTarget:previewOutput.imageView];
}
if (_histogramGenerator != nil && !self.standalone) {
[_finalFilter addTarget:_histogramGenerator];
}
}
}
#pragma mark - Result
- (void)createResultImageWithCompletion:(void (^)(UIImage *image))completion
{
[self processAnimated:false capture:true synchronous:false completion:^
{
UIImage *image = [_finalFilter imageFromCurrentFramebufferWithOrientation:UIImageOrientationUp];
if (completion != nil)
completion(image);
}];
}
- (UIImage *)currentResultImage
{
__block UIImage *image = nil;
[self processAnimated:false capture:true synchronous:true completion:^
{
image = [_finalFilter imageFromCurrentFramebufferWithOrientation:UIImageOrientationUp];
}];
return image;
}
- (void)currentResultCIImage:(void (^)(CIImage *image, void(^unlock)(void)))completion
{
[self processAnimated:false capture:true synchronous:true completion:^
{
[_finalFilter newCIImageFromCurrentlyProcessedOutput:completion];
}];
}
#pragma mark - Editor Values
- (void)importAdjustments:(id<TGMediaEditAdjustments>)adjustments
{
_initialAdjustments = adjustments;
if (adjustments != nil)
self.cropRect = adjustments.cropRect;
self.cropOrientation = adjustments.cropOrientation;
self.cropLockedAspectRatio = adjustments.cropLockedAspectRatio;
self.cropMirrored = adjustments.cropMirrored;
self.paintingData = adjustments.paintingData;
if ([adjustments isKindOfClass:[PGPhotoEditorValues class]])
{
PGPhotoEditorValues *editorValues = (PGPhotoEditorValues *)adjustments;
self.cropRotation = editorValues.cropRotation;
}
else if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]])
{
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
self.trimStartValue = videoAdjustments.trimStartValue;
self.trimEndValue = videoAdjustments.trimEndValue;
self.sendAsGif = videoAdjustments.sendAsGif;
self.preset = videoAdjustments.preset;
}
for (PGPhotoTool *tool in self.tools)
{
id value = adjustments.toolValues[tool.identifier];
if (value != nil && [value isKindOfClass:[tool valueClass]])
tool.value = [value copy];
}
}
- (id<TGMediaEditAdjustments>)exportAdjustments
{
return [self exportAdjustmentsWithPaintingData:_paintingData];
}
- (id<TGMediaEditAdjustments>)exportAdjustmentsWithPaintingData:(TGPaintingData *)paintingData
{
NSMutableDictionary *toolValues = [[NSMutableDictionary alloc] init];
for (PGPhotoTool *tool in self.tools)
{
if (!tool.shouldBeSkipped && (!_forVideo || tool.isAvialableForVideo))
{
if (!([tool.value isKindOfClass:[NSNumber class]] && ABS([tool.value floatValue] - (float)tool.defaultValue) < FLT_EPSILON))
toolValues[tool.identifier] = [tool.value copy];
}
}
if (!_forVideo)
{
return [PGPhotoEditorValues editorValuesWithOriginalSize:self.originalSize cropRect:self.cropRect cropRotation:self.cropRotation cropOrientation:self.cropOrientation cropLockedAspectRatio:self.cropLockedAspectRatio cropMirrored:self.cropMirrored toolValues:toolValues paintingData:paintingData sendAsGif:self.sendAsGif];
}
else
{
TGVideoEditAdjustments *initialAdjustments = (TGVideoEditAdjustments *)_initialAdjustments;
return [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:self.originalSize cropRect:self.cropRect cropOrientation:self.cropOrientation cropRotation:self.cropRotation cropLockedAspectRatio:self.cropLockedAspectRatio cropMirrored:self.cropMirrored trimStartValue:initialAdjustments.trimStartValue trimEndValue:initialAdjustments.trimEndValue toolValues:toolValues paintingData:paintingData sendAsGif:self.sendAsGif preset:self.preset];
}
}
- (void)setDisableAll:(bool)disableAll {
_disableAll = disableAll;
for (PGPhotoTool *tool in self.tools)
{
tool.disabled = disableAll;
}
}
- (SSignal *)histogramSignal
{
return [[SSignal single:_currentHistogram] then:_histogramPipe.signalProducer()];
}
+ (NSArray *)availableTools
{
static NSArray *tools;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
tools = @[ [PGEnhanceTool class],
[PGExposureTool class],
[PGContrastTool class],
[PGSaturationTool class],
[PGWarmthTool class],
[PGFadeTool class],
[PGTintTool class],
[PGHighlightsTool class],
[PGShadowsTool class],
[PGVignetteTool class],
[PGGrainTool class],
[PGBlurTool class],
[PGSharpenTool class],
[PGCurvesTool class] ];
});
return tools;
}
+ (UIImage *)resultImageForImage:(UIImage *)image adjustments:(id<TGMediaEditAdjustments>)adjustments {
PGPhotoEditor *editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:false enableStickers:true];
editor.standalone = true;
[editor setImage:image forCropRect:adjustments.cropRect cropRotation:0.0 cropOrientation:adjustments.cropOrientation cropMirrored:adjustments.cropMirrored fullSize:false];
return [editor currentResultImage];
}
@end