Swiftgram/submodules/LegacyComponents/Sources/TGPhotoEditorController.m
2023-09-22 18:09:36 +04:00

3071 lines
120 KiB
Objective-C

#import "TGPhotoEditorController.h"
#import "LegacyComponentsInternal.h"
#import <objc/runtime.h>
#import <Photos/Photos.h>
#import <LegacyComponents/TGPhotoEditorAnimation.h>
#import "TGPhotoEditorInterfaceAssets.h"
#import <LegacyComponents/TGPhotoEditorUtils.h>
#import <LegacyComponents/TGPaintUtils.h>
#import <LegacyComponents/UIImage+TG.h>
#import "TGProgressWindow.h"
#import "PGPhotoEditor.h"
#import "PGPhotoEditorView.h"
#import "TGPaintFaceDetector.h"
#import <LegacyComponents/PGPhotoEditorValues.h>
#import <LegacyComponents/TGVideoEditAdjustments.h>
#import <LegacyComponents/TGPaintingData.h>
#import <LegacyComponents/TGMediaVideoConverter.h>
#import "TGPhotoToolbarView.h"
#import "TGPhotoEditorPreviewView.h"
#import <LegacyComponents/TGMenuView.h>
#import <LegacyComponents/TGMediaAssetsLibrary.h>
#import <LegacyComponents/TGMediaAssetImageSignals.h>
#import "TGPhotoCropController.h"
#import "TGPhotoToolsController.h"
#import "TGPhotoDrawingController.h"
#import "TGPhotoQualityController.h"
#import "TGPhotoAvatarPreviewController.h"
#import "TGPhotoAvatarCropView.h"
#import "TGMessageImageViewOverlayView.h"
#import "TGMediaPickerGalleryVideoScrubber.h"
#import "TGMediaPickerGalleryVideoScrubberThumbnailView.h"
#import "TGMenuSheetController.h"
#import <LegacyComponents/AVURLAsset+TGMediaItem.h>
#import "TGCameraCapturedVideo.h"
@interface TGPhotoEditorController () <TGViewControllerNavigationBarAppearance, TGMediaPickerGalleryVideoScrubberDataSource, TGMediaPickerGalleryVideoScrubberDelegate, UIDocumentInteractionControllerDelegate>
{
bool _switchingTab;
TGPhotoEditorTab _availableTabs;
TGPhotoEditorTab _currentTab;
TGPhotoEditorTabController *_currentTabController;
TGMediaEditingContext *_standaloneEditingContext;
UIView *_backgroundView;
UIView *_containerView;
UIView *_wrapperView;
UIView *_transitionWrapperView;
TGPhotoToolbarView *_portraitToolbarView;
TGPhotoToolbarView *_landscapeToolbarView;
TGPhotoEditorPreviewView *_previewView;
PGPhotoEditorView *_fullPreviewView;
UIView<TGPhotoDrawingEntitiesView> *_fullEntitiesView;
UIImageView *_fullPaintingView;
PGPhotoEditor *_photoEditor;
SQueue *_queue;
TGPhotoEditorControllerIntent _intent;
id<TGMediaEditableItem> _item;
UIImage *_screenImage;
UIImage *_thumbnailImage;
CMTime _chaseTime;
bool _chaseStart;
bool _chasingTime;
bool _isPlaying;
AVPlayerItem *_playerItem;
SMetaDisposable *_playerItemDisposable;
id _playerStartedObserver;
id _playerReachedEndObserver;
bool _registeredKeypathObserver;
NSTimer *_positionTimer;
bool _scheduledVideoPlayback;
id<TGMediaEditAdjustments> _initialAdjustments;
NSAttributedString *_caption;
bool _viewFillingWholeScreen;
bool _forceStatusBarVisible;
bool _ignoreDefaultPreviewViewTransitionIn;
bool _hasOpenedPhotoTools;
bool _hiddenToolbarView;
UIDocumentInteractionController *_documentController;
bool _dismissed;
bool _hadProgress;
bool _progressVisible;
TGMessageImageViewOverlayView *_progressView;
SMetaDisposable *_faceDetectorDisposable;
bool _wasPlaying;
bool _initializedScrubber;
NSArray *_cachedThumbnails;
TGMediaPickerGalleryVideoScrubber *_scrubberView;
bool _resetDotPosition;
NSTimeInterval _dotPosition;
UIImageView *_dotMarkerView;
TGMediaPickerGalleryVideoScrubberThumbnailView *_dotImageView;
UIView *_dotImageSnapshotView;
bool _requestingThumbnails;
SMetaDisposable *_thumbnailsDisposable;
id<LegacyComponentsContext> _context;
}
@property (nonatomic, weak) UIImage *fullSizeImage;
@end
@implementation TGPhotoEditorController
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context item:(id<TGMediaEditableItem>)item intent:(TGPhotoEditorControllerIntent)intent adjustments:(id<TGMediaEditAdjustments>)adjustments caption:(NSAttributedString *)caption screenImage:(UIImage *)screenImage availableTabs:(TGPhotoEditorTab)availableTabs selectedTab:(TGPhotoEditorTab)selectedTab
{
self = [super initWithContext:context];
if (self != nil)
{
_context = context;
self.automaticallyManageScrollViewInsets = false;
self.autoManageStatusBarBackground = false;
self.isImportant = true;
_availableTabs = availableTabs;
_item = item;
_currentTab = selectedTab;
_intent = intent;
_caption = caption;
_initialAdjustments = adjustments;
_screenImage = screenImage;
CGSize originalSize = _item.originalSize;
if ([self presentedForAvatarCreation]) {
CGFloat maxSide = [GPUImageContext maximumTextureSizeForThisDevice];
if (MAX(_item.originalSize.width, _item.originalSize.height) > maxSide) {
originalSize = TGScaleToFit(_item.originalSize, CGSizeMake(maxSide, maxSide));
}
}
_queue = [[SQueue alloc] init];
_photoEditor = [[PGPhotoEditor alloc] initWithOriginalSize:originalSize adjustments:adjustments forVideo:item.isVideo enableStickers:(intent & TGPhotoEditorControllerSignupAvatarIntent) == 0];
if ([self presentedForAvatarCreation])
{
_photoEditor.cropOnLast = true;
CGFloat shortSide = MIN(originalSize.width, originalSize.height);
_photoEditor.cropRect = CGRectMake((originalSize.width - shortSide) / 2, (originalSize.height - shortSide) / 2, shortSide, shortSide);
}
if ([adjustments isKindOfClass:[TGVideoEditAdjustments class]])
{
TGVideoEditAdjustments *videoAdjustments = (TGVideoEditAdjustments *)adjustments;
_photoEditor.trimStartValue = videoAdjustments.trimStartValue;
_photoEditor.trimEndValue = videoAdjustments.trimEndValue;
}
_thumbnailsDisposable = [[SMetaDisposable alloc] init];
_chaseTime = kCMTimeInvalid;
self.customAppearanceMethodsForwarding = true;
}
return self;
}
- (void)dealloc
{
[_faceDetectorDisposable dispose];
[_thumbnailsDisposable dispose];
}
- (void)loadView
{
[super loadView];
self.view.frame = (CGRect){ CGPointZero, [self referenceViewSize]};
self.view.clipsToBounds = true;
if (@available(iOS 11.0, *)) {
self.view.accessibilityIgnoresInvertColors = true;
}
if ([self presentedForAvatarCreation] && ![self presentedFromCamera])
self.view.backgroundColor = [UIColor blackColor];
_wrapperView = [[UIView alloc] initWithFrame:CGRectZero];
[self.view addSubview:_wrapperView];
_backgroundView = [[UIView alloc] initWithFrame:_wrapperView.bounds];
_backgroundView.alpha = 0.0f;
_backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_backgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarBackgroundColor];
[_wrapperView addSubview:_backgroundView];
_transitionWrapperView = [[UIView alloc] initWithFrame:_wrapperView.bounds];
[_wrapperView addSubview:_transitionWrapperView];
_containerView = [[UIView alloc] initWithFrame:CGRectZero];
[_wrapperView addSubview:_containerView];
_progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 60.0f, 60.0f)];
[_progressView setRadius:60.0];
_progressView.userInteractionEnabled = false;
__weak TGPhotoEditorController *weakSelf = self;
void(^toolbarCancelPressed)(void) = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf cancelButtonPressed];
};
void(^toolbarDonePressed)(void) = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf doneButtonPressed];
};
void(^toolbarDoneLongPressed)(id) = ^(id sender)
{
};
void(^toolbarTabPressed)(TGPhotoEditorTab) = ^(TGPhotoEditorTab tab)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
switch (tab)
{
default:
[strongSelf presentTab:tab];
break;
case TGPhotoEditorToolsTab:
case TGPhotoEditorBlurTab:
case TGPhotoEditorCurvesTab:
case TGPhotoEditorTintTab:
if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoToolsController class]])
[strongSelf->_currentTabController handleTabAction:tab];
else
[strongSelf presentTab:TGPhotoEditorToolsTab];
break;
case TGPhotoEditorPaintTab:
case TGPhotoEditorEraserTab:
[strongSelf presentTab:TGPhotoEditorPaintTab];
break;
case TGPhotoEditorStickerTab:
case TGPhotoEditorTextTab:
[strongSelf->_currentTabController handleTabAction:tab];
break;
case TGPhotoEditorRotateTab:
case TGPhotoEditorMirrorTab:
case TGPhotoEditorAspectRatioTab:
if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoCropController class]] || [strongSelf->_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]])
[strongSelf->_currentTabController handleTabAction:tab];
break;
}
};
TGPhotoEditorBackButton backButton = TGPhotoEditorBackButtonCancel;
TGPhotoEditorDoneButton doneButton = TGPhotoEditorDoneButtonCheck;
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true];
[_portraitToolbarView setToolbarTabs:_availableTabs animated:false];
[_portraitToolbarView setActiveTab:_currentTab];
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
_portraitToolbarView.donePressed = toolbarDonePressed;
_portraitToolbarView.doneLongPressed = toolbarDoneLongPressed;
_portraitToolbarView.tabPressed = toolbarTabPressed;
[_wrapperView addSubview:_portraitToolbarView];
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true];
[_landscapeToolbarView setToolbarTabs:_availableTabs animated:false];
[_landscapeToolbarView setActiveTab:_currentTab];
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
_landscapeToolbarView.donePressed = toolbarDonePressed;
_landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed;
_landscapeToolbarView.tabPressed = toolbarTabPressed;
if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad)
[_wrapperView addSubview:_landscapeToolbarView];
if ((_intent & TGPhotoEditorControllerWebIntent) || (_intent & TGPhotoEditorControllerAvatarIntent && _item.isVideo))
[self updateDoneButtonEnabled:false animated:false];
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:TGPhotoEditorToolbarSize orientation:self.effectiveOrientation panelSize:TGPhotoEditorPanelSize hasOnScreenNavigation:self.hasOnScreenNavigation];
CGSize fittedSize = TGScaleToSize(_photoEditor.rotatedCropSize, containerFrame.size);
_previewView = [[TGPhotoEditorPreviewView alloc] initWithFrame:CGRectMake(0, 0, fittedSize.width, fittedSize.height)];
_previewView.clipsToBounds = true;
[_previewView setSnapshotImage:_screenImage];
[_photoEditor setPreviewOutput:_previewView];
[self updatePreviewView:true];
if ([self presentedForAvatarCreation]) {
_previewView.applyMirror = true;
CGSize fittedSize = TGScaleToSize(_photoEditor.originalSize, CGSizeMake(1024, 1024));
_fullPreviewView = [[PGPhotoEditorView alloc] initWithFrame:CGRectMake(0, 0, fittedSize.width, fittedSize.height)];
_photoEditor.additionalOutputs = @[_fullPreviewView];
[self.view addSubview:_fullPreviewView];
_fullPaintingView = [[UIImageView alloc] init];
_fullPaintingView.frame = _fullPreviewView.frame;
CGSize size = TGScaleToSize(_photoEditor.originalSize, [TGPhotoDrawingController maximumPaintingSize]);
_fullEntitiesView = [_stickersContext drawingEntitiesViewWithSize:size];
_fullEntitiesView.userInteractionEnabled = false;
CGRect rect = [TGPhotoDrawingController fittedCropRect:_photoEditor.cropRect originalSize:_photoEditor.originalSize keepOriginalSize:true];
_fullEntitiesView.frame = CGRectMake(0, 0, rect.size.width, rect.size.height);
}
_dotMarkerView = [[UIImageView alloc] initWithImage:TGCircleImage(7.0, [TGPhotoEditorInterfaceAssets accentColor])];
[_scrubberView addSubview:_dotMarkerView];
_dotMarkerView.center = CGPointMake(30.0, -20.0);
_dotImageView = [[TGMediaPickerGalleryVideoScrubberThumbnailView alloc] initWithImage:nil originalSize:_photoEditor.originalSize cropRect:CGRectZero cropOrientation:UIImageOrientationUp cropMirrored:false];
_dotImageView.frame = CGRectMake(0.0, 0.0, 160.0, 160.0);
_dotImageView.userInteractionEnabled = true;
CAShapeLayer* maskLayer = [CAShapeLayer new];
maskLayer.frame = _dotImageView.bounds;
maskLayer.path = [UIBezierPath bezierPathWithOvalInRect:_dotImageView.bounds].CGPath;
_dotImageView.layer.mask = maskLayer;
UITapGestureRecognizer *dotTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDotTap)];
[_dotImageView addGestureRecognizer:dotTapRecognizer];
if ([self presentedForAvatarCreation] && _item.isVideo) {
_scrubberView = [[TGMediaPickerGalleryVideoScrubber alloc] initWithFrame:CGRectMake(0.0f, 0.0, _portraitToolbarView.frame.size.width, 68.0f)];
_scrubberView.minimumLength = 3.0;
_scrubberView.layer.allowsGroupOpacity = true;
_scrubberView.hasDotPicker = true;
_scrubberView.dataSource = self;
_scrubberView.delegate = self;
_scrubberView.clipsToBounds = false;
}
[self detectFaces];
[self presentTab:_currentTab];
}
- (void)handleDotTap {
TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController;
if (![previewController isKindOfClass:[TGPhotoAvatarPreviewController class]])
return;
[self stopVideoPlayback:false];
[self seekVideo:_dotPosition];
[previewController beginScrubbing:false];
[_scrubberView setValue:_dotPosition resetPosition:true];
__weak TGPhotoEditorController *weakSelf = self;
[previewController endScrubbing:false completion:^bool{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
return !strongSelf->_scrubberView.isScrubbing;
}];
}
- (void)setToolbarHidden:(bool)hidden animated:(bool)animated
{
if (self.requestToolbarsHidden == nil)
return;
if (_hiddenToolbarView == hidden)
return;
if (hidden)
{
[_portraitToolbarView transitionOutAnimated:animated transparent:true hideOnCompletion:false];
[_landscapeToolbarView transitionOutAnimated:animated transparent:true hideOnCompletion:false];
}
else
{
[_portraitToolbarView transitionInAnimated:animated transparent:true];
[_landscapeToolbarView transitionInAnimated:animated transparent:true];
}
self.requestToolbarsHidden(hidden, animated);
_hiddenToolbarView = hidden;
}
- (BOOL)prefersStatusBarHidden
{
if (_forceStatusBarVisible)
return false;
if ([self inFormSheet])
return false;
if (self.navigationController != nil)
return _viewFillingWholeScreen;
if (self.dontHideStatusBar)
return false;
return true;
}
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
{
return [_currentTabController preferredScreenEdgesDeferringSystemGestures];
}
- (UIBarStyle)requiredNavigationBarStyle
{
return UIBarStyleDefault;
}
- (bool)navigationBarShouldBeHidden
{
return true;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if ([_currentTabController isKindOfClass:[TGPhotoCropController class]])
return;
if (self.item.isVideo) {
_scrubberView.allowsTrimming = self.item.originalDuration >= TGVideoEditMinimumTrimmableDuration;
_scrubberView.disableZoom = true;
_scrubberView.disableTimeDisplay = true;
_scrubberView.trimStartValue = 0.0;
_scrubberView.trimEndValue = MIN(9.9, self.item.originalDuration);
[_scrubberView setTrimApplied:self.item.originalDuration > 9.9];
_scrubberView.maximumLength = 9.9;
[self setVideoEndTime:_scrubberView.trimEndValue];
}
NSTimeInterval position = 0;
TGMediaVideoEditAdjustments *adjustments = [_photoEditor exportAdjustments];
if ([adjustments isKindOfClass:[TGMediaVideoEditAdjustments class]])
position = adjustments.trimStartValue;
PGPhotoEditor *photoEditor = _photoEditor;
CGSize screenSize = TGNativeScreenSize();
SSignal *signal = nil;
if ([_photoEditor hasDefaultCropping] && (NSInteger)screenSize.width == 320)
{
signal = [self.requestOriginalScreenSizeImage(_item, position) filter:^bool(id image)
{
return [image isKindOfClass:[UIImage class]];
}];
}
else
{
if (_item.isVideo) {
signal = [self.requestOriginalFullSizeImage(_item, position) deliverOn:_queue];
} else {
bool avatar = [self presentedForAvatarCreation];
signal = [[[[self.requestOriginalFullSizeImage(_item, position) takeLast] deliverOn:_queue] filter:^bool(id image)
{
return [image isKindOfClass:[UIImage class]];
}] map:^UIImage *(UIImage *image)
{
if (avatar) {
CGFloat maxSide = MIN(TGPhotoEditorResultImageAvatarMaxSize.width, [GPUImageContext maximumTextureSizeForThisDevice]);
if (MAX(image.size.width, image.size.height) > maxSide) {
CGSize fittedSize = TGScaleToFit(image.size, CGSizeMake(maxSide, maxSide));
return TGScaleImageToPixelSize(image, fittedSize);
} else {
return image;
}
} else {
return TGPhotoEditorCrop(image, nil, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropRect, photoEditor.cropMirrored, TGPhotoEditorScreenImageMaxSize(), photoEditor.originalSize, true);
}
}];
}
}
__weak TGPhotoEditorController *weakSelf = self;
[signal startWithNext:^(id next)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_dismissed)
return;
CGFloat progress = 0.0;
bool progressVisible = false;
bool doneEnabled = true;
if ([next isKindOfClass:[UIImage class]]) {
[photoEditor setImage:(UIImage *)next forCropRect:photoEditor.cropRect cropRotation:photoEditor.cropRotation cropOrientation:photoEditor.cropOrientation cropMirrored:photoEditor.cropMirrored fullSize:false];
if (!((UIImage *)next).degraded) {
progress = 1.0f;
}
} else if ([next isKindOfClass:[AVAsset class]]) {
strongSelf->_playerItem = [AVPlayerItem playerItemWithAsset:(AVAsset *)next];
strongSelf->_player = [AVPlayer playerWithPlayerItem:strongSelf->_playerItem];
strongSelf->_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
strongSelf->_player.muted = true;
[photoEditor setPlayerItem:strongSelf->_playerItem forCropRect:photoEditor.cropRect cropRotation:0.0 cropOrientation:photoEditor.cropOrientation cropMirrored:photoEditor.cropMirrored];
TGDispatchOnMainThread(^
{
[strongSelf->_previewView performTransitionInWithCompletion:^{}];
if (strongSelf->_scheduledVideoPlayback) {
strongSelf->_scheduledVideoPlayback = false;
[strongSelf startVideoPlayback:true];
}
});
progress = 1.0f;
doneEnabled = true;
} else if ([next isKindOfClass:[NSNumber class]]) {
progress = [next floatValue];
progressVisible = true;
doneEnabled = false;
}
TGDispatchOnMainThread(^{
if (strongSelf->_dismissed)
return;
[strongSelf setProgressVisible:progressVisible value:progress animated:progressVisible];
[strongSelf updateDoneButtonEnabled:doneEnabled animated:true];
if (progressVisible)
strongSelf->_hadProgress = true;
if (strongSelf->_hadProgress && !progressVisible) {
[strongSelf->_progressView setPlay];
[strongSelf->_scrubberView reloadThumbnails];
}
});
if ([next isKindOfClass:[NSNumber class]]) {
return;
}
if (strongSelf->_ignoreDefaultPreviewViewTransitionIn)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGDispatchOnMainThread(^
{
if (strongSelf->_dismissed)
return;
if ([strongSelf->_currentTabController isKindOfClass:[TGPhotoQualityController class]])
[strongSelf->_previewView setSnapshotImageOnTransition:next];
else
[strongSelf->_previewView setSnapshotImage:next];
});
}
else
{
[photoEditor processAnimated:false completion:^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
TGDispatchOnMainThread(^
{
if (strongSelf->_dismissed)
return;
[strongSelf->_previewView performTransitionInWithCompletion:^
{
if (!strongSelf.skipInitialTransition)
[strongSelf->_previewView setSnapshotImage:next];
}];
});
}];
}
}];
}
- (NSTimeInterval)trimStartValue {
if (_scrubberView != nil) {
return _scrubberView.trimStartValue;
} else {
return _photoEditor.trimStartValue;
}
}
- (NSTimeInterval)trimEndValue {
if (_scrubberView != nil) {
if (_scrubberView.trimEndValue > 0.0)
return _scrubberView.trimEndValue;
else
return MIN(9.9, _scrubberView.duration);
} else {
return _photoEditor.trimEndValue;
}
}
- (void)_setupPlaybackStartedObserver
{
CMTime startTime = CMTimeMake(10, 100);
if (self.trimStartValue > DBL_EPSILON)
startTime = CMTimeMakeWithSeconds(self.trimStartValue + 0.1, NSEC_PER_SEC);
__weak TGPhotoEditorController *weakSelf = self;
_playerStartedObserver = [_player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:startTime]] queue:NULL usingBlock:^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_player removeTimeObserver:strongSelf->_playerStartedObserver];
strongSelf->_playerStartedObserver = nil;
if (CMTimeGetSeconds(strongSelf->_player.currentItem.duration) > 0)
[strongSelf _setupPlaybackReachedEndObserver];
}];
}
- (void)_setupPlaybackReachedEndObserver
{
if (_playerReachedEndObserver != nil)
[_player removeTimeObserver:_playerReachedEndObserver];
CMTime endTime = CMTimeSubtract(_player.currentItem.duration, CMTimeMake(10, 100));
if (self.trimEndValue > DBL_EPSILON && self.trimEndValue < CMTimeGetSeconds(_player.currentItem.duration))
endTime = CMTimeMakeWithSeconds(self.trimEndValue - 0.1, NSEC_PER_SEC);
CMTime startTime = CMTimeMake(5, 100);
if (self.trimStartValue > DBL_EPSILON)
startTime = CMTimeMakeWithSeconds(self.trimStartValue + 0.05, NSEC_PER_SEC);
__weak TGPhotoEditorController *weakSelf = self;
_playerReachedEndObserver = [_player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:endTime]] queue:NULL usingBlock:^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf != nil && !strongSelf->_dismissed) {
[strongSelf->_player seekToTime:startTime];
[strongSelf->_scrubberView setValue:strongSelf.trimStartValue resetPosition:true];
[strongSelf->_fullEntitiesView seekTo:0.0];
[strongSelf->_fullEntitiesView play];
}
}];
}
- (void)returnFullPreviewView {
_fullPreviewView.frame = CGRectMake(-10000, 0, _fullPreviewView.frame.size.width, _fullPreviewView.frame.size.height);
[self.view addSubview:_fullPreviewView];
}
- (void)startVideoPlayback:(bool)reset {
if (reset && _player == nil) {
_scheduledVideoPlayback = true;
return;
}
if (reset) {
NSTimeInterval startPosition = 0.0f;
if (self.trimStartValue > DBL_EPSILON)
startPosition = self.trimStartValue;
CMTime targetTime = CMTimeMakeWithSeconds(startPosition, NSEC_PER_SEC);
[_player.currentItem seekToTime:targetTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:nil];
[self _setupPlaybackStartedObserver];
if (!_registeredKeypathObserver) {
[_player addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:nil];
_registeredKeypathObserver = true;
}
[_fullEntitiesView seekTo:0.0];
[_fullEntitiesView play];
} else {
[_fullEntitiesView play];
}
_isPlaying = true;
[_player play];
[_positionTimer invalidate];
_positionTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(positionTimerEvent) interval:0.25 repeat:true];
[self positionTimerEvent];
}
- (void)stopVideoPlayback:(bool)reset {
if (reset) {
if (_playerStartedObserver != nil)
[_player removeTimeObserver:_playerStartedObserver];
if (_playerReachedEndObserver != nil)
[_player removeTimeObserver:_playerReachedEndObserver];
if (_registeredKeypathObserver) {
[_player removeObserver:self forKeyPath:@"rate" context:nil];
_registeredKeypathObserver = false;
}
[_scrubberView setIsPlaying:false];
} else {
[_fullEntitiesView pause];
}
_isPlaying = false;
[_player pause];
[_positionTimer invalidate];
_positionTimer = nil;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)__unused change context:(void *)__unused context
{
if (object == _player && [keyPath isEqualToString:@"rate"])
{
if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) {
[_scrubberView setIsPlaying:_player.rate > FLT_EPSILON];
}
}
}
- (void)positionTimerEvent
{
if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) {
[_scrubberView setValue:CMTimeGetSeconds(_player.currentItem.currentTime) resetPosition:false];
}
}
- (NSTimeInterval)currentTime {
return CMTimeGetSeconds(_player.currentItem.currentTime) - [self trimStartValue];
}
- (void)setMinimalVideoDuration:(NSTimeInterval)duration {
_scrubberView.minimumLength = duration;
}
- (void)seekVideo:(NSTimeInterval)position {
CMTime targetTime = CMTimeMakeWithSeconds(position, NSEC_PER_SEC);
if (CMTIME_COMPARE_INLINE(targetTime, !=, _chaseTime))
{
_chaseTime = targetTime;
if (!_chasingTime) {
[self chaseTime];
}
}
}
- (void)chaseTime {
_chasingTime = true;
CMTime currentChasingTime = _chaseTime;
[_player.currentItem seekToTime:currentChasingTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
if (!_chaseStart) {
TGDispatchOnMainThread(^{
[_fullEntitiesView seekTo:CMTimeGetSeconds(currentChasingTime) - _scrubberView.trimStartValue];
});
}
if (CMTIME_COMPARE_INLINE(currentChasingTime, ==, _chaseTime)) {
_chasingTime = false;
_chaseTime = kCMTimeInvalid;
} else {
[self chaseTime];
}
}];
}
- (void)setVideoEndTime:(NSTimeInterval)endTime {
_player.currentItem.forwardPlaybackEndTime = CMTimeMakeWithSeconds(endTime, NSEC_PER_SEC);
[self _setupPlaybackReachedEndObserver];
}
- (void)viewWillAppear:(BOOL)animated
{
if (_dismissed)
return;
if (![self inFormSheet] && (self.navigationController != nil || self.dontHideStatusBar))
{
if (animated)
{
[UIView animateWithDuration:0.3 animations:^
{
[_context setApplicationStatusBarAlpha:0.0f];
}];
}
else
{
[_context setApplicationStatusBarAlpha:0.0f];
}
}
else if (!self.dontHideStatusBar)
{
if (iosMajorVersion() < 7) {
[_context forceSetStatusBarHidden:true withAnimation:UIStatusBarAnimationNone];
}
}
[super viewWillAppear:animated];
[self transitionIn];
}
- (void)viewDidAppear:(BOOL)animated
{
if (_dismissed)
return;
if (self.navigationController != nil)
{
_viewFillingWholeScreen = true;
if ([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)])
[self setNeedsStatusBarAppearanceUpdate];
else
[_context forceSetStatusBarHidden:[self prefersStatusBarHidden] withAnimation:UIStatusBarAnimationNone];
self.navigationController.interactivePopGestureRecognizer.enabled = false;
}
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
if (self.navigationController != nil || self.dontHideStatusBar)
{
_viewFillingWholeScreen = false;
if ([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)])
[self setNeedsStatusBarAppearanceUpdate];
else
[_context forceSetStatusBarHidden:[self prefersStatusBarHidden] withAnimation:UIStatusBarAnimationNone];
TGDispatchAfter(0.1, dispatch_get_main_queue(), ^{
if (animated)
{
[UIView animateWithDuration:0.3 animations:^
{
[_context setApplicationStatusBarAlpha:1.0f];
}];
}
else
{
[_context setApplicationStatusBarAlpha:1.0f];
}
});
self.navigationController.interactivePopGestureRecognizer.enabled = true;
}
if (@available(iOS 11.0, *)) {
if ([self respondsToSelector:@selector(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)])
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
[super viewWillDisappear:animated];
}
- (void)updateDoneButtonEnabled:(bool)enabled animated:(bool)animated
{
[_portraitToolbarView setEditButtonsEnabled:enabled animated:animated];
[_landscapeToolbarView setEditButtonsEnabled:enabled animated:animated];
[_portraitToolbarView setDoneButtonEnabled:enabled animated:animated];
[_landscapeToolbarView setDoneButtonEnabled:enabled animated:animated];
if (animated) {
[UIView animateWithDuration:0.2 animations:^{
_scrubberView.alpha = enabled ? 1.0 : 0.2;
}];
} else {
_scrubberView.alpha = enabled ? 1.0 : 0.2;
}
_scrubberView.userInteractionEnabled = enabled;
}
- (void)updateStatusBarAppearanceForDismiss
{
_forceStatusBarVisible = true;
if ([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)])
[self setNeedsStatusBarAppearanceUpdate];
else
[_context forceSetStatusBarHidden:[self prefersStatusBarHidden] withAnimation:UIStatusBarAnimationNone];
}
- (BOOL)shouldAutorotate
{
return (!(_currentTabController != nil && ![_currentTabController shouldAutorotate]) && [super shouldAutorotate]);
}
#pragma mark -
- (void)createEditedImageWithEditorValues:(id<TGMediaEditAdjustments>)editorValues createThumbnail:(bool)createThumbnail saveOnly:(bool)saveOnly completion:(void (^)(UIImage *))completion
{
bool avatar = [self presentedForAvatarCreation];
if (!saveOnly)
{
if (!avatar && [editorValues isDefaultValuesForAvatar:false])
{
if (self.willFinishEditing != nil)
self.willFinishEditing(nil, [_currentTabController currentResultRepresentation], true);
if (self.didFinishEditing != nil)
self.didFinishEditing(nil, nil, nil, true, ^{});
if (completion != nil)
completion(nil);
return;
}
}
if (!saveOnly && self.willFinishEditing != nil)
self.willFinishEditing(editorValues, [_currentTabController currentResultRepresentation], true);
if (!saveOnly && !avatar && completion != nil)
completion(nil);
UIImage *fullSizeImage = self.fullSizeImage;
PGPhotoEditor *photoEditor = _photoEditor;
SSignal *imageSignal = nil;
if (fullSizeImage == nil)
{
imageSignal = [[[self.requestOriginalFullSizeImage(_item, 0) filter:^bool(id result)
{
return [result isKindOfClass:[UIImage class]];
}] takeLast] map:^UIImage *(UIImage *image) {
if (avatar) {
CGFloat maxSide = [GPUImageContext maximumTextureSizeForThisDevice];
if (MAX(image.size.width, image.size.height) > maxSide) {
CGSize fittedSize = TGScaleToFit(image.size, CGSizeMake(maxSide, maxSide));
return TGScaleImageToPixelSize(image, fittedSize);
} else {
return image;
}
} else {
return image;
}
}];
}
else
{
imageSignal = [SSignal single:fullSizeImage];
}
bool hasImageAdjustments = editorValues.toolsApplied || saveOnly;
bool hasPainting = editorValues.hasPainting;
bool hasAnimation = editorValues.paintingData.hasAnimation;
bool ignoreCropForResult = self.ignoreCropForResult;
SSignal *(^imageCropSignal)(UIImage *, bool) = ^(UIImage *image, bool resize)
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
if (ignoreCropForResult) {
if (image.size.width > TGPhotoEditorResultImageWallpaperMaxSize.width || image.size.height > TGPhotoEditorResultImageWallpaperMaxSize.width) {
[subscriber putNext:TGPhotoEditorFitImage(image, TGPhotoEditorResultImageWallpaperMaxSize)];
} else {
[subscriber putNext:image];
}
} else {
UIImage *paintingImage = !hasImageAdjustments ? editorValues.paintingData.image : nil;
UIImage *croppedImage = TGPhotoEditorCrop(image, paintingImage, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropRect, photoEditor.cropMirrored, TGPhotoEditorResultImageMaxSize, photoEditor.originalSize, resize);
[subscriber putNext:croppedImage];
}
[subscriber putCompletion];
return nil;
}];
};
SQueue *queue = _queue;
SSignal *(^imageRenderSignal)(UIImage *) = ^(UIImage *image)
{
return [[SSignal alloc] initWithGenerator:^id<SDisposable>(SSubscriber *subscriber)
{
[photoEditor setImage:image forCropRect:photoEditor.cropRect cropRotation:photoEditor.cropRotation cropOrientation:photoEditor.cropOrientation cropMirrored:photoEditor.cropMirrored fullSize:true];
[photoEditor createResultImageWithCompletion:^(UIImage *result)
{
[queue dispatch:^{
UIImage *final = result;
if (hasPainting)
{
final = TGPaintCombineCroppedImages(final, editorValues.paintingData.image, true, photoEditor.originalSize, photoEditor.cropRect, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropMirrored);
[TGPaintingData facilitatePaintingData:editorValues.paintingData];
}
[subscriber putNext:final];
[subscriber putCompletion];
}];
}];
return nil;
}];
};
SSignal *renderedImageSignal = [[imageSignal mapToSignal:^SSignal *(UIImage *image)
{
return [imageCropSignal(image, !hasImageAdjustments || hasPainting || MAX(image.size.width, image.size.height) > 4096) startOn:_queue];
}] mapToSignal:^SSignal *(UIImage *image)
{
if (hasImageAdjustments)
return [[[SSignal complete] delay:0.3 onQueue:queue] then:imageRenderSignal(image)];
else
return [SSignal single:image];
}];
if (saveOnly)
{
[[renderedImageSignal deliverOn:[SQueue mainQueue]] startWithNext:^(UIImage *image)
{
if (completion != nil)
completion(image);
}];
}
else
{
void (^didFinishRenderingFullSizeImage)(UIImage *) = self.didFinishRenderingFullSizeImage;
void (^didFinishEditing)(id<TGMediaEditAdjustments>, UIImage *, UIImage *, bool , void(^)(void)) = self.didFinishEditing;
TGPhotoEditorControllerIntent intent = _intent;
[[[[renderedImageSignal map:^id(UIImage *image)
{
if (!hasImageAdjustments)
{
if (didFinishRenderingFullSizeImage != nil) {
if (hasPainting && !hasAnimation) {
didFinishRenderingFullSizeImage(image);
} else if (intent == TGPhotoEditorControllerWallpaperIntent) {
didFinishRenderingFullSizeImage(nil);
}
}
return image;
}
else
{
if (!saveOnly && !hasAnimation && didFinishRenderingFullSizeImage != nil)
didFinishRenderingFullSizeImage(image);
return TGPhotoEditorFitImage(image, TGPhotoEditorResultImageMaxSize);
}
}] map:^NSDictionary *(UIImage *image)
{
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
if (image != nil)
result[@"image"] = image;
if (createThumbnail)
{
CGSize fillSize = TGPhotoThumbnailSizeForCurrentScreen();
fillSize.width = CGCeil(fillSize.width);
fillSize.height = CGCeil(fillSize.height);
CGSize size = TGScaleToFillSize(image.size, fillSize);
UIGraphicsBeginImageContextWithOptions(size, true, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (thumbnailImage != nil)
result[@"thumbnail"] = thumbnailImage;
}
return result;
}] deliverOn:[SQueue mainQueue]] startWithNext:^(NSDictionary *result)
{
UIImage *image = result[@"image"];
UIImage *thumbnailImage = result[@"thumbnail"];
if (avatar && image.size.width < 150.0) {
image = TGScaleImageToPixelSize(image, CGSizeMake(150.0, 150.0));
}
if (!saveOnly && didFinishEditing != nil) {
didFinishEditing(editorValues, image, thumbnailImage, true, ^{
if (avatar && completion != nil) {
completion(image);
}
});
}
} error:^(__unused id error)
{
TGLegacyLog(@"renderedImageSignal error");
} completed:nil];
}
}
#pragma mark - Intent
- (bool)presentedFromCamera
{
return _intent & TGPhotoEditorControllerFromCameraIntent;
}
- (bool)presentedForAvatarCreation
{
return _intent & (TGPhotoEditorControllerAvatarIntent | TGPhotoEditorControllerSignupAvatarIntent);
}
- (bool)presentedForForumAvatarCreation
{
return _intent & (TGPhotoEditorControllerForumAvatarIntent);
}
- (bool)presentedForSuggestedAvatar
{
return _intent & (TGPhotoEditorControllerSuggestedAvatarIntent);
}
- (bool)presentedForSuggestingAvatar
{
return _intent & (TGPhotoEditorControllerSuggestingAvatarIntent);
}
#pragma mark - Transition
- (void)transitionIn
{
if (self.navigationController != nil)
return;
CGFloat delay = [self presentedFromCamera] ? 0.1f: 0.0f;
_portraitToolbarView.alpha = 0.0f;
_landscapeToolbarView.alpha = 0.0f;
[UIView animateWithDuration:0.3f delay:delay options:UIViewAnimationOptionCurveLinear animations:^
{
_portraitToolbarView.alpha = 1.0f;
_landscapeToolbarView.alpha = 1.0f;
} completion:nil];
if (_intent == TGPhotoEditorControllerWallpaperIntent) {
[UIView animateWithDuration:0.25f delay:0.15 options:UIViewAnimationOptionCurveLinear animations:^
{
_backgroundView.alpha = 1.0f;
} completion:nil];
}
}
- (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion
{
_dismissed = true;
if (!saving) {
[self stopVideoPlayback:true];
}
[UIView animateWithDuration:0.3f animations:^
{
_portraitToolbarView.alpha = 0.0f;
_landscapeToolbarView.alpha = 0.0f;
}];
if (_intent == TGPhotoEditorControllerWallpaperIntent) {
[UIView animateWithDuration:0.1f animations:^
{
_backgroundView.alpha = 0.0f;
}];
}
_currentTabController.beginTransitionOut = self.beginTransitionOut;
[self setToolbarHidden:false animated:true];
if (self.beginCustomTransitionOut != nil && !saving)
{
id rep = [_currentTabController currentResultRepresentation];
if ([rep isKindOfClass:[UIImage class]])
{
UIImageView *imageView = [[UIImageView alloc] initWithImage:(UIImage *)rep];
rep = imageView;
}
[_currentTabController prepareForCustomTransitionOut];
TGPhotoEditorTabController *tabController = _currentTabController;
self.beginCustomTransitionOut([_currentTabController transitionOutReferenceFrame], rep, ^{
[tabController finishCustomTransitionOut];
if (completion)
completion();
});
}
else
{
[_currentTabController transitionOutSaving:saving completion:^
{
if (completion != nil)
completion();
if (self.finishedTransitionOut != nil)
self.finishedTransitionOut(saving);
}];
}
}
- (void)presentTab:(TGPhotoEditorTab)tab
{
if (_switchingTab || (tab == _currentTab && _currentTabController != nil))
return;
bool isInitialAppearance = true;
CGRect transitionReferenceFrame = CGRectZero;
UIView *transitionReferenceView = nil;
UIView *transitionParentView = nil;
bool transitionNoTransitionView = false;
UIImage *snapshotImage = nil;
UIView *snapshotView = nil;
TGPhotoEditorTabController *currentController = _currentTabController;
TGPhotoEditorTab switchingFromTab = TGPhotoEditorNoneTab;
if (currentController != nil)
{
if (![currentController isDismissAllowed])
return;
[self savePaintingData];
bool resetTransform = false;
if ([self presentedForAvatarCreation] && tab == TGPhotoEditorCropTab && [currentController isKindOfClass:[TGPhotoDrawingController class]]) {
resetTransform = true;
}
currentController.switchingToTab = tab;
[currentController transitionOutSwitching:true completion:^
{
[currentController removeFromParentViewController];
[currentController.view removeFromSuperview];
if (resetTransform) {
_previewView.transform = CGAffineTransformIdentity;
}
}];
transitionReferenceFrame = [currentController transitionOutReferenceFrame];
transitionReferenceView = [currentController transitionOutReferenceView];
transitionNoTransitionView = false;
if ([currentController isKindOfClass:[TGPhotoCropController class]])
{
_backgroundView.alpha = 1.0f;
[UIView animateWithDuration:0.3f animations:^
{
_backgroundView.alpha = 0.0f;
} completion:nil];
switchingFromTab = TGPhotoEditorCropTab;
} else if ([currentController isKindOfClass:[TGPhotoToolsController class]]) {
switchingFromTab = TGPhotoEditorToolsTab;
}
isInitialAppearance = false;
snapshotView = [currentController snapshotView];
}
else
{
if (self.beginTransitionIn != nil)
transitionReferenceView = self.beginTransitionIn(&transitionReferenceFrame, &transitionParentView);
if ([self presentedFromCamera] && [self presentedForAvatarCreation])
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (self.interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
{
transitionReferenceFrame = CGRectMake(self.view.frame.size.width - transitionReferenceFrame.size.height - transitionReferenceFrame.origin.y,
transitionReferenceFrame.origin.x,
transitionReferenceFrame.size.height, transitionReferenceFrame.size.width);
}
else if (self.interfaceOrientation == UIInterfaceOrientationLandscapeRight)
{
transitionReferenceFrame = CGRectMake(transitionReferenceFrame.origin.y,
self.view.frame.size.height - transitionReferenceFrame.size.width - transitionReferenceFrame.origin.x,
transitionReferenceFrame.size.height, transitionReferenceFrame.size.width);
}
#pragma clang diagnostic pop
}
if ([self presentedForAvatarCreation] && ![self presentedFromCamera])
transitionNoTransitionView = true;
snapshotImage = _screenImage;
}
if (_currentTabController == nil && self.skipInitialTransition) {
[self presentAnimated:true];
}
_switchingTab = true;
if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) {
if (_item.isVideo && !_isPlaying) {
[self setPlayButtonHidden:true animated:false];
[self startVideoPlayback:false];
} else if (!_item.isVideo) {
[_photoEditor processAnimated:false completion:nil];
}
}
TGPhotoEditorBackButton backButtonType = TGPhotoEditorBackButtonCancel;
TGPhotoEditorDoneButton doneButtonType = TGPhotoEditorDoneButtonCheck;
bool sideButtonsHiddenInCrop = [self presentedForSuggestedAvatar] && !_item.isVideo;
__weak TGPhotoEditorController *weakSelf = self;
TGPhotoEditorTabController *controller = nil;
switch (tab)
{
case TGPhotoEditorCropTab:
{
_fullPaintingView.hidden = false;
[self updatePreviewView:true];
__block UIView *initialBackgroundView = nil;
if ([self presentedForAvatarCreation])
{
[_containerView.superview insertSubview:_containerView belowSubview:_portraitToolbarView];
bool skipInitialTransition = (![self presentedFromCamera] && self.navigationController != nil) || self.skipInitialTransition;
TGPhotoAvatarPreviewController *cropController = [[TGPhotoAvatarPreviewController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView isForum:[self presentedForForumAvatarCreation] isSuggestion:[self presentedForSuggestedAvatar] isSuggesting:[self presentedForSuggestingAvatar] senderName:self.senderName];
cropController.stickersContext = _stickersContext;
cropController.scrubberView = _scrubberView;
cropController.dotImageView = _dotImageView;
cropController.dotMarkerView = _dotMarkerView;
cropController.fullPreviewView = _fullPreviewView;
cropController.fullPaintingView = _fullPaintingView;
cropController.fullEntitiesView = _fullEntitiesView;
cropController.fullEntitiesView.userInteractionEnabled = false;
cropController.fromCamera = [self presentedFromCamera];
cropController.skipTransitionIn = skipInitialTransition;
if (snapshotImage != nil)
[cropController setSnapshotImage:snapshotImage];
cropController.toolbarLandscapeSize = TGPhotoEditorToolbarSize;
cropController.controlVideoPlayback = ^(bool play) {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil || strongSelf->_progressVisible)
return;
if (play) {
[strongSelf startVideoPlayback:false];
} else {
[strongSelf stopVideoPlayback:false];
}
};
cropController.isVideoPlaying = ^bool{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
return strongSelf->_isPlaying;
};
cropController.togglePlayback = ^{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil || !strongSelf->_item.isVideo || strongSelf->_progressVisible)
return;
if (strongSelf->_isPlaying) {
[strongSelf stopVideoPlayback:false];
[strongSelf setPlayButtonHidden:false animated:true];
} else {
[strongSelf startVideoPlayback:false];
[strongSelf setPlayButtonHidden:true animated:true];
}
};
cropController.croppingChanged = ^{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf->_scrubberView updateThumbnails];
strongSelf->_dotImageView.cropRect = strongSelf->_photoEditor.cropRect;
strongSelf->_dotImageView.cropOrientation = strongSelf->_photoEditor.cropOrientation;
strongSelf->_dotImageView.cropMirrored = strongSelf->_photoEditor.cropMirrored;
[strongSelf->_dotImageView updateCropping:true];
[strongSelf updatePreviewView:false];
}
};
cropController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
*referenceFrame = transitionReferenceFrame;
*noTransitionView = transitionNoTransitionView;
*parentView = transitionParentView;
if (strongSelf != nil)
{
UIView *backgroundView = nil;
if (!skipInitialTransition)
{
UIView *backgroundSuperview = transitionParentView;
if (backgroundSuperview == nil)
backgroundSuperview = transitionReferenceView.superview.superview;
initialBackgroundView = [[UIView alloc] initWithFrame:backgroundSuperview.bounds];
initialBackgroundView.alpha = 0.0f;
initialBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarBackgroundColor];
[backgroundSuperview addSubview:initialBackgroundView];
backgroundView = initialBackgroundView;
}
else
{
backgroundView = strongSelf->_backgroundView;
}
[UIView animateWithDuration:0.3f animations:^
{
backgroundView.alpha = 1.0f;
}];
}
return transitionReferenceView;
};
cropController.finishedTransitionIn = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (!skipInitialTransition)
{
[initialBackgroundView removeFromSuperview];
if (strongSelf.finishedTransitionIn != nil)
strongSelf.finishedTransitionIn();
}
else
{
strongSelf->_backgroundView.alpha = 0.0f;
}
strongSelf->_switchingTab = false;
if (isInitialAppearance)
[strongSelf startVideoPlayback:true];
};
cropController.finishedTransitionOut = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_fullPaintingView.hidden = true;
if (strongSelf->_currentTabController.finishedTransitionIn != nil) {
strongSelf->_currentTabController.finishedTransitionIn();
strongSelf->_currentTabController.finishedTransitionIn = nil;
}
[strongSelf->_currentTabController _finishedTransitionInWithView:nil];
[strongSelf returnFullPreviewView];
};
cropController.cancelPressed = ^{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_portraitToolbarView.cancelPressed();
};
cropController.donePressed = ^{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf->_portraitToolbarView.donePressed();
};
controller = cropController;
doneButtonType = TGPhotoEditorDoneButtonDone;
if (sideButtonsHiddenInCrop) {
[_portraitToolbarView setCancelDoneButtonsHidden:true animated:true];
[_portraitToolbarView setCenterButtonsHidden:false animated:true];
[_landscapeToolbarView setAllButtonsHidden:false animated:true];
} else {
[_portraitToolbarView setAllButtonsHidden:false animated:false];
[_landscapeToolbarView setAllButtonsHidden:false animated:false];
}
}
else
{
TGPhotoCropController *cropController = [[TGPhotoCropController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView metadata:self.metadata forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
if (snapshotView != nil)
[cropController setSnapshotView:snapshotView];
else if (snapshotImage != nil)
[cropController setSnapshotImage:snapshotImage];
cropController.toolbarLandscapeSize = TGPhotoEditorToolbarSize;
cropController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView)
{
*referenceFrame = transitionReferenceFrame;
*noTransitionView = transitionNoTransitionView;
*parentView = transitionParentView;
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf != nil)
{
UIView *backgroundView = nil;
if (isInitialAppearance)
{
UIView *backgroundSuperview = transitionParentView;
if (backgroundSuperview == nil)
backgroundSuperview = transitionReferenceView.superview.superview;
initialBackgroundView = [[UIView alloc] initWithFrame:backgroundSuperview.bounds];
initialBackgroundView.alpha = 0.0f;
initialBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarBackgroundColor];
[backgroundSuperview addSubview:initialBackgroundView];
backgroundView = initialBackgroundView;
}
else
{
backgroundView = strongSelf->_backgroundView;
}
[UIView animateWithDuration:0.3f animations:^
{
backgroundView.alpha = 1.0f;
}];
}
return transitionReferenceView;
};
cropController.finishedTransitionIn = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (isInitialAppearance)
{
[initialBackgroundView removeFromSuperview];
if (strongSelf.finishedTransitionIn != nil)
strongSelf.finishedTransitionIn();
}
else
{
strongSelf->_backgroundView.alpha = 0.0f;
}
strongSelf->_switchingTab = false;
};
cropController.cropReset = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf reset];
};
if (_intent != TGPhotoEditorControllerVideoIntent)
{
[[self.requestOriginalFullSizeImage(_item, 0) deliverOn:[SQueue mainQueue]] startWithNext:^(UIImage *image)
{
if (cropController.dismissing && !cropController.switching)
return;
if (![image isKindOfClass:[UIImage class]] || image.degraded)
return;
self.fullSizeImage = image;
[cropController setImage:image];
}];
}
else if (self.requestImage != nil)
{
UIImage *image = self.requestImage();
[cropController setImage:image];
}
controller = cropController;
}
}
break;
case TGPhotoEditorPaintTab:
{
[_portraitToolbarView setAllButtonsHidden:true animated:[self presentedForAvatarCreation]];
[_landscapeToolbarView setAllButtonsHidden:true animated:[self presentedForAvatarCreation]];
[_containerView.superview bringSubviewToFront:_containerView];
TGPhotoDrawingController *drawingController = [[TGPhotoDrawingController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView entitiesView:_fullEntitiesView stickersContext:_stickersContext isAvatar:[self presentedForAvatarCreation]];
drawingController.requestDismiss = ^{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf dismissEditor];
};
drawingController.requestApply = ^{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if ([strongSelf presentedForAvatarCreation]) {
[strongSelf presentTab:TGPhotoEditorCropTab];
} else {
[strongSelf applyEditor];
}
};
drawingController.toolbarLandscapeSize = TGPhotoEditorToolbarSize;
drawingController.controlVideoPlayback = ^(bool play) {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (play) {
[strongSelf startVideoPlayback:false];
} else {
[strongSelf stopVideoPlayback:false];
}
};
drawingController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return nil;
*referenceFrame = transitionReferenceFrame;
*parentView = transitionParentView;
*noTransitionView = transitionNoTransitionView;
return transitionReferenceView;
};
drawingController.finishedTransitionIn = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (isInitialAppearance && strongSelf.finishedTransitionIn != nil)
strongSelf.finishedTransitionIn();
strongSelf->_switchingTab = false;
if (isInitialAppearance)
[strongSelf startVideoPlayback:true];
};
drawingController.finishedTransitionOut = ^{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf->_containerView.superview insertSubview:strongSelf->_containerView atIndex:2];
[strongSelf->_portraitToolbarView setAllButtonsHidden:false animated:true];
[strongSelf->_landscapeToolbarView setAllButtonsHidden:false animated:true];
};
controller = drawingController;
}
break;
case TGPhotoEditorToolsTab:
{
if ([self presentedForSuggestedAvatar]) {
[_portraitToolbarView setCancelDoneButtonsHidden:false animated:true];
}
TGPhotoToolsController *toolsController = [[TGPhotoToolsController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView entitiesView:_fullEntitiesView];
toolsController.toolbarLandscapeSize = TGPhotoEditorToolbarSize;
toolsController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView)
{
*referenceFrame = transitionReferenceFrame;
*parentView = transitionParentView;
*noTransitionView = transitionNoTransitionView;
return transitionReferenceView;
};
toolsController.finishedTransitionIn = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (isInitialAppearance && strongSelf.finishedTransitionIn != nil)
strongSelf.finishedTransitionIn();
strongSelf->_switchingTab = false;
if (isInitialAppearance)
[strongSelf startVideoPlayback:true];
};
controller = toolsController;
}
break;
case TGPhotoEditorQualityTab:
{
_ignoreDefaultPreviewViewTransitionIn = true;
TGPhotoQualityController *qualityController = [[TGPhotoQualityController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView];
qualityController.item = _item;
qualityController.toolbarLandscapeSize = TGPhotoEditorToolbarSize;
qualityController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView)
{
*referenceFrame = transitionReferenceFrame;
*parentView = transitionParentView;
*noTransitionView = transitionNoTransitionView;
return transitionReferenceView;
};
qualityController.finishedTransitionIn = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (isInitialAppearance && strongSelf.finishedTransitionIn != nil)
strongSelf.finishedTransitionIn();
strongSelf->_switchingTab = false;
strongSelf->_ignoreDefaultPreviewViewTransitionIn = false;
};
controller = qualityController;
}
break;
default:
break;
}
if ([self presentedForAvatarCreation] && !isInitialAppearance && tab != TGPhotoEditorCropTab) {
backButtonType = TGPhotoEditorBackButtonBack;
}
_currentTabController = controller;
_currentTabController.item = _item;
_currentTabController.intent = _intent;
_currentTabController.switchingFromTab = switchingFromTab;
_currentTabController.initialAppearance = isInitialAppearance;
if (![_currentTabController isKindOfClass:[TGPhotoDrawingController class]])
_currentTabController.availableTabs = _availableTabs;
if ([self presentedForAvatarCreation] && self.navigationController == nil)
_currentTabController.transitionSpeed = 20.0f;
[self addChildViewController:_currentTabController];
[_containerView addSubview:_currentTabController.view];
if (currentController != nil)
[_currentTabController viewWillAppear:true];
_currentTabController.view.frame = _containerView.bounds;
if (currentController != nil)
[_currentTabController viewDidAppear:true];
_currentTabController.valuesChanged = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf updatePreviewView:true];
};
_currentTabController.tabsChanged = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf != nil)
[strongSelf updateEditorButtons];
};
_currentTab = tab;
[_portraitToolbarView setToolbarTabs:[_currentTabController availableTabs] animated:true];
[_landscapeToolbarView setToolbarTabs:[_currentTabController availableTabs] animated:true];
[_portraitToolbarView setBackButtonType:backButtonType];
[_landscapeToolbarView setBackButtonType:backButtonType];
[_portraitToolbarView setDoneButtonType:doneButtonType];
[_landscapeToolbarView setDoneButtonType:doneButtonType];
[self updateEditorButtons];
if (@available(iOS 11.0, *)) {
if ([self respondsToSelector:@selector(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)])
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
- (void)updatePreviewView:(bool)full
{
if (full) {
[_previewView setPaintingImageWithData:_photoEditor.paintingData];
_fullPaintingView.image = _photoEditor.paintingData.image;
}
UIImageOrientation cropOrientation = _photoEditor.cropOrientation;
if ([self presentedForAvatarCreation]) {
cropOrientation = UIImageOrientationUp;
}
[_previewView setCropRect:_photoEditor.cropRect cropOrientation:cropOrientation cropRotation:_photoEditor.cropRotation cropMirrored:_photoEditor.cropMirrored originalSize:_photoEditor.originalSize];
}
- (void)updateEditorButtons
{
TGPhotoEditorTab activeTab = TGPhotoEditorNoneTab;
activeTab = [_currentTabController activeTab];
[_portraitToolbarView setActiveTab:activeTab];
[_landscapeToolbarView setActiveTab:activeTab];
TGPhotoEditorTab highlightedTabs = TGPhotoEditorNoneTab;
highlightedTabs = [_currentTabController highlightedTabs];
[_portraitToolbarView setEditButtonsHighlighted:highlightedTabs];
[_landscapeToolbarView setEditButtonsHighlighted:highlightedTabs];
}
#pragma mark - Crop
- (void)reset
{
if (_intent != TGPhotoEditorControllerVideoIntent)
return;
TGPhotoCropController *cropController = (TGPhotoCropController *)_currentTabController;
if (![cropController isKindOfClass:[TGPhotoCropController class]])
return;
}
#pragma mark -
- (void)presentAnimated:(bool)animated
{
if (animated)
{
const CGFloat velocity = 2000.0f;
CGFloat duration = self.view.frame.size.height / velocity;
CGRect targetFrame = self.view.frame;
self.view.frame = CGRectOffset(self.view.frame, 0, self.view.frame.size.height);
[UIView animateWithDuration:duration animations:^
{
self.view.frame = targetFrame;
} completion:^(__unused BOOL finished)
{
TGDispatchAfter(1.0, dispatch_get_main_queue(), ^{
[_photoEditor updateProcessChain:true];
});
}];
}
}
- (void)dismissAnimated:(bool)animated
{
_dismissed = true;
self.view.userInteractionEnabled = false;
if (self.navigationController != nil)
animated = false;
if (animated)
{
const CGFloat velocity = 2000.0f;
CGFloat duration = self.view.frame.size.height / velocity;
CGRect targetFrame = CGRectOffset(self.view.frame, 0, self.view.frame.size.height);
[UIView animateWithDuration:duration delay:0.4 options:kNilOptions animations:^
{
self.view.frame = targetFrame;
} completion:^(__unused BOOL finished)
{
[_currentTabController finishCustomTransitionOut];
if (self.navigationController != nil) {
[self.navigationController popViewControllerAnimated:false];
} else {
[self dismiss];
if (self.onDismiss)
self.onDismiss();
}
}];
}
else
{
if (self.navigationController != nil) {
[_currentTabController finishCustomTransitionOut];
[self.navigationController popViewControllerAnimated:false];
} else {
[self dismiss];
}
}
}
- (void)cancelButtonPressed
{
[self dismissEditor];
}
- (void)dismissEditor
{
if (![_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]] && [self presentedForAvatarCreation]) {
[self presentTab:TGPhotoEditorCropTab];
return;
}
if (![_currentTabController isDismissAllowed])
return;
__weak TGPhotoEditorController *weakSelf = self;
void(^dismiss)(void) = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
strongSelf.view.userInteractionEnabled = false;
[strongSelf->_currentTabController prepareTransitionOutSaving:false];
if (self.skipInitialTransition) {
[strongSelf dismissAnimated:true];
} else if (strongSelf.navigationController != nil && [strongSelf.navigationController.viewControllers containsObject:strongSelf])
{
[strongSelf.navigationController popViewControllerAnimated:true];
}
else
{
[strongSelf transitionOutSaving:false completion:^
{
[strongSelf dismiss];
}];
}
if (strongSelf.willFinishEditing != nil)
strongSelf.willFinishEditing(nil, nil, false);
if (strongSelf.didFinishEditing != nil)
strongSelf.didFinishEditing(nil, nil, nil, false, ^{});
};
if ([_currentTabController isKindOfClass:[TGPhotoDrawingController class]]) {
dismiss();
return;
}
TGPaintingData *paintingData = nil;
if ([_currentTabController isKindOfClass:[TGPhotoDrawingController class]])
paintingData = [(TGPhotoDrawingController *)_currentTabController paintingData];
PGPhotoEditorValues *editorValues = paintingData == nil ? [_photoEditor exportAdjustments] : [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
if ((_initialAdjustments == nil && (![editorValues isDefaultValuesForAvatar:[self presentedForAvatarCreation]] || editorValues.cropOrientation != UIImageOrientationUp)) || (_initialAdjustments != nil && ![editorValues isEqual:_initialAdjustments]))
{
TGMenuSheetController *controller = [[TGMenuSheetController alloc] initWithContext:_context dark:false];
controller.dismissesByOutsideTap = true;
controller.narrowInLandscape = true;
__weak TGMenuSheetController *weakController = controller;
NSArray *items = @
[
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"PhotoEditor.DiscardChanges") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
{
__strong TGMenuSheetController *strongController = weakController;
if (strongController == nil)
return;
[strongController dismissAnimated:true manual:false completion:^
{
dismiss();
}];
}],
[[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.Cancel") type:TGMenuSheetButtonTypeCancel fontSize:20.0 action:^
{
__strong TGMenuSheetController *strongController = weakController;
if (strongController != nil)
[strongController dismissAnimated:true];
}]
];
[controller setItemViews:items];
controller.sourceRect = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return CGRectZero;
if (UIInterfaceOrientationIsPortrait(strongSelf.effectiveOrientation))
return [strongSelf.view convertRect:strongSelf->_portraitToolbarView.cancelButtonFrame fromView:strongSelf->_portraitToolbarView];
else
return [strongSelf.view convertRect:strongSelf->_landscapeToolbarView.cancelButtonFrame fromView:strongSelf->_landscapeToolbarView];
};
[controller presentInViewController:self sourceView:self.view animated:true];
}
else
{
dismiss();
}
}
- (void)doneButtonPressed
{
if ([self presentedForAvatarCreation] && ![_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) {
[self presentTab:TGPhotoEditorCropTab];
} else {
[self applyEditor];
}
}
- (void)savePaintingData {
if (![_currentTabController isKindOfClass:[TGPhotoDrawingController class]])
return;
TGPhotoDrawingController *paintController = (TGPhotoDrawingController *)_currentTabController;
TGPaintingData *paintingData = [paintController paintingData];
_photoEditor.paintingData = paintingData;
if (paintingData != nil)
[TGPaintingData storePaintingData:paintingData inContext:self.editingContext forItem:_item forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
[_previewView setPaintingImageWithData:_photoEditor.paintingData];
[_previewView setPaintingHidden:false];
}
- (void)applyEditor
{
if (![_currentTabController isDismissAllowed])
return;
bool forAvatar = [self presentedForAvatarCreation];
if (!forAvatar) {
self.view.userInteractionEnabled = false;
}
[_currentTabController prepareTransitionOutSaving:true];
bool saving = true;
NSTimeInterval videoStartValue = 0.0;
NSTimeInterval trimStartValue = 0.0;
NSTimeInterval trimEndValue = 0.0;
if ([_currentTabController isKindOfClass:[TGPhotoDrawingController class]])
{
[self savePaintingData];
}
else if ([_currentTabController isKindOfClass:[TGPhotoQualityController class]])
{
TGPhotoQualityController *qualityController = (TGPhotoQualityController *)_currentTabController;
_photoEditor.preset = qualityController.preset;
saving = false;
[[NSUserDefaults standardUserDefaults] setObject:@(qualityController.preset) forKey:@"TG_preferredVideoPreset_v0"];
} else if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]])
{
videoStartValue = _dotPosition;
trimStartValue = self.trimStartValue;
trimEndValue = MIN(self.trimStartValue + 9.9, self.trimEndValue);
}
[self stopVideoPlayback:true];
TGPaintingData *paintingData = _photoEditor.paintingData;
TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
if ([self presentedForAvatarCreation] && _item.isVideo) {
[[SQueue concurrentDefaultQueue] dispatch:^
{
id<TGMediaEditableItem> item = _item;
SSignal *assetSignal = [SSignal complete];
if ([item isKindOfClass:[TGMediaAsset class]])
assetSignal = [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)item];
else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
assetSignal = ((TGCameraCapturedVideo *)item).avAsset;
[assetSignal startWithNext:^(AVURLAsset *asset)
{
CGSize videoDimensions = CGSizeZero;
if ([item isKindOfClass:[TGMediaAsset class]])
videoDimensions = ((TGMediaAsset *)item).dimensions;
else if ([asset isKindOfClass:[AVURLAsset class]])
videoDimensions = ((AVURLAsset *)asset).originalSize;
AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generator.appliesPreferredTrackTransform = true;
generator.requestedTimeToleranceAfter = kCMTimeZero;
generator.requestedTimeToleranceBefore = kCMTimeZero;
CGImageRef imageRef = [generator copyCGImageAtTime:CMTimeMakeWithSeconds(MIN(videoStartValue, CMTimeGetSeconds(asset.duration) - 0.05), NSEC_PER_SEC) actualTime:nil error:NULL];
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
UIImage *paintingImage = adjustments.paintingData.stillImage;
if (paintingImage == nil) {
paintingImage = adjustments.paintingData.image;
}
UIImage *fullImage = nil;
if (adjustments.toolsApplied) {
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
if ([self presentedForAvatarCreation]) {
fullImage = TGPhotoEditorVideoCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, CGSizeMake(640, 640), item.originalSize, true, false);
} else {
CGSize fillSize = TGScaleToFillSize(videoDimensions, image.size);
UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
[image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
[paintingImage drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
fullImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
} else {
fullImage = TGPhotoEditorVideoCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, CGSizeMake(640, 640), item.originalSize, true, false);
}
NSTimeInterval duration = trimEndValue - trimStartValue;
TGMediaVideoConversionPreset preset;
if (duration > 0.0) {
if (duration <= 2.0) {
preset = TGMediaVideoConversionPresetProfileVeryHigh;
} else if (duration <= 5.0) {
preset = TGMediaVideoConversionPresetProfileHigh;
} else if (duration <= 8.0) {
preset = TGMediaVideoConversionPresetProfile;
} else {
preset = TGMediaVideoConversionPresetProfileLow;
}
} else {
preset = TGMediaVideoConversionPresetProfile;
}
TGDispatchOnMainThread(^{
if (self.didFinishEditingVideo != nil)
self.didFinishEditingVideo(asset, [adjustments editAdjustmentsWithPreset:preset videoStartValue:videoStartValue trimStartValue:trimStartValue trimEndValue:trimEndValue], fullImage, nil, true, ^{
[self dismissAnimated:true];
});
});
}];
}];
return;
}
else if (_intent != TGPhotoEditorControllerVideoIntent)
{
TGProgressWindow *progressWindow;
if (!forAvatar) {
progressWindow = [[TGProgressWindow alloc] init];
progressWindow.windowLevel = self.view.window.windowLevel + 0.001f;
[progressWindow performSelector:@selector(showAnimated) withObject:nil afterDelay:0.5];
}
[self createEditedImageWithEditorValues:adjustments createThumbnail:!forAvatar saveOnly:false completion:^(__unused UIImage *image)
{
[NSObject cancelPreviousPerformRequestsWithTarget:progressWindow selector:@selector(showAnimated) object:nil];
[progressWindow dismiss:true];
if (forAvatar) {
[self dismissAnimated:true];
return;
}
[self transitionOutSaving:true completion:^
{
[self dismiss];
}];
}];
}
else
{
bool hasChanges = !(_initialAdjustments == nil && [adjustments isDefaultValuesForAvatar:false] && adjustments.cropOrientation == UIImageOrientationUp);
if (adjustments.paintingData != nil || adjustments.hasPainting != _initialAdjustments.hasPainting || adjustments.toolsApplied)
{
[[SQueue concurrentDefaultQueue] dispatch:^
{
id<TGMediaEditableItem> item = _item;
SSignal *assetSignal = [SSignal complete];
if ([item isKindOfClass:[TGMediaAsset class]])
assetSignal = [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)item];
else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
assetSignal = ((TGCameraCapturedVideo *)item).avAsset;
[assetSignal startWithNext:^(AVAsset *asset)
{
CGSize videoDimensions = CGSizeZero;
if ([item isKindOfClass:[TGMediaAsset class]])
videoDimensions = ((TGMediaAsset *)item).dimensions;
else if ([asset isKindOfClass:[AVURLAsset class]])
videoDimensions = ((AVURLAsset *)asset).originalSize;
AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generator.appliesPreferredTrackTransform = true;
generator.maximumSize = TGFitSize(videoDimensions, CGSizeMake(1280.0f, 1280.0f));
generator.requestedTimeToleranceAfter = kCMTimeZero;
generator.requestedTimeToleranceBefore = kCMTimeZero;
CGImageRef imageRef = [generator copyCGImageAtTime:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC) actualTime:nil error:NULL];
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
if (adjustments.toolsApplied) {
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
}
UIImage *paintingImage = adjustments.paintingData.stillImage;
if (paintingImage == nil) {
paintingImage = adjustments.paintingData.image;
}
CGSize fillSize = TGScaleToFillSize(videoDimensions, image.size);
UIImage *fullImage = nil;
UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
[image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
[paintingImage drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
fullImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGSize thumbnailSize = TGPhotoThumbnailSizeForCurrentScreen();
thumbnailSize.width = CGCeil(thumbnailSize.width);
thumbnailSize.height = CGCeil(thumbnailSize.height);
fillSize = TGScaleToFillSize(videoDimensions, thumbnailSize);
UIImage *thumbnailImage = nil;
UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f);
context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
[image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
[paintingImage drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.editingContext setImage:fullImage thumbnailImage:thumbnailImage forItem:_item synchronous:true];
}];
}];
}
if (self.willFinishEditing != nil)
self.willFinishEditing(hasChanges ? adjustments : nil, nil, hasChanges);
if (self.didFinishEditing != nil) {
self.didFinishEditing(hasChanges ? adjustments : nil, nil, nil, hasChanges, ^{
if ([self presentedForAvatarCreation]) {
[self dismissAnimated:true];
} else {
[self transitionOutSaving:saving completion:^{
[self dismiss];
}];
}
});
}
}
}
- (TGMediaEditingContext *)editingContext
{
if (_editingContext) {
return _editingContext;
} else {
if (_standaloneEditingContext == nil) {
_standaloneEditingContext = [[TGMediaEditingContext alloc] init];
}
return _standaloneEditingContext;
}
}
#pragma mark - External Export
- (void)_saveToCameraRoll
{
TGProgressWindow *progressWindow = [[TGProgressWindow alloc] init];
progressWindow.windowLevel = self.view.window.windowLevel + 0.001f;
[progressWindow performSelector:@selector(showAnimated) withObject:nil afterDelay:0.5];
TGPaintingData *paintingData = nil;
if ([_currentTabController isKindOfClass:[TGPhotoDrawingController class]])
paintingData = [(TGPhotoDrawingController *)_currentTabController paintingData];
PGPhotoEditorValues *editorValues = paintingData == nil ? [_photoEditor exportAdjustments] : [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
[self createEditedImageWithEditorValues:editorValues createThumbnail:false saveOnly:true completion:^(UIImage *resultImage)
{
[[[[TGMediaAssetsLibrary sharedLibrary] saveAssetWithImage:resultImage] deliverOn:[SQueue mainQueue]] startWithNext:nil completed:^
{
[NSObject cancelPreviousPerformRequestsWithTarget:progressWindow selector:@selector(showAnimated) object:nil];
[progressWindow dismissWithSuccess];
}];
}];
}
- (void)_openInInstagram
{
TGProgressWindow *progressWindow = [[TGProgressWindow alloc] init];
progressWindow.windowLevel = self.view.window.windowLevel + 0.001f;
[progressWindow performSelector:@selector(showAnimated) withObject:nil afterDelay:0.5];
TGPaintingData *paintingData = nil;
if ([_currentTabController isKindOfClass:[TGPhotoDrawingController class]])
paintingData = [(TGPhotoDrawingController *)_currentTabController paintingData];
PGPhotoEditorValues *editorValues = paintingData == nil ? [_photoEditor exportAdjustments] : [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
[self createEditedImageWithEditorValues:editorValues createThumbnail:false saveOnly:true completion:^(UIImage *resultImage)
{
[NSObject cancelPreviousPerformRequestsWithTarget:progressWindow selector:@selector(showAnimated) object:nil];
[progressWindow dismiss:true];
NSData *imageData = UIImageJPEGRepresentation(resultImage, 0.9);
NSString *writePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"instagram.igo"];
if (![imageData writeToFile:writePath atomically:true])
{
return;
}
NSURL *fileURL = [NSURL fileURLWithPath:writePath];
_documentController = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
_documentController.delegate = self;
[_documentController setUTI:@"com.instagram.exclusivegram"];
if (_caption.length > 0)
[_documentController setAnnotation:@{@"InstagramCaption" : _caption.string}];
[_documentController presentOpenInMenuFromRect:self.view.frame inView:self.view animated:true];
}];
}
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)__unused controller
{
_documentController = nil;
}
#pragma mark -
- (void)dismiss
{
if (self.overlayWindow != nil || self.customDismissBlock != nil)
{
[super dismiss];
}
else
{
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
}
#pragma mark - Layout
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self.view setNeedsLayout];
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self updateLayout:[[LegacyComponentsGlobals provider] applicationStatusBarOrientation]];
}
- (bool)inFormSheet
{
if (iosMajorVersion() < 9)
return [super inFormSheet];
UIUserInterfaceSizeClass sizeClass = [_context currentHorizontalSizeClass];
if (sizeClass == UIUserInterfaceSizeClassCompact)
return false;
return [super inFormSheet];
}
- (CGSize)referenceViewSize
{
if ([self inFormSheet])
return CGSizeMake(540.0f, 620.0f);
if (self.parentViewController != nil)
return self.parentViewController.view.frame.size;
else if (self.navigationController != nil)
return self.navigationController.view.frame.size;
return [_context fullscreenBounds].size;
}
- (bool)hasOnScreenNavigation {
bool hasOnScreenNavigation = false;
if (@available(iOS 11.0, *)) {
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
}
return hasOnScreenNavigation;
}
- (UIInterfaceOrientation)effectiveOrientation {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return [self effectiveOrientation:self.interfaceOrientation];
#pragma clang diagnostic pop
}
- (UIInterfaceOrientation)effectiveOrientation:(UIInterfaceOrientation)orientation {
bool isPad = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
if ([self inFormSheet] || isPad)
orientation = UIInterfaceOrientationPortrait;
return orientation;
}
- (UIEdgeInsets)screenEdges {
CGSize referenceSize = [self referenceViewSize];
CGFloat screenSide = MAX(referenceSize.width, referenceSize.height);
return UIEdgeInsetsMake((screenSide - referenceSize.height) / 2, (screenSide - referenceSize.width) / 2, (screenSide + referenceSize.height) / 2, (screenSide + referenceSize.width) / 2);
}
- (void)updateLayout:(UIInterfaceOrientation)orientation
{
orientation = [self effectiveOrientation:orientation];
CGSize referenceSize = [self referenceViewSize];
CGFloat screenSide = MAX(referenceSize.width, referenceSize.height);
_wrapperView.frame = CGRectMake((referenceSize.width - screenSide) / 2, (referenceSize.height - screenSide) / 2, screenSide, screenSide);
_containerView.frame = CGRectMake((screenSide - referenceSize.width) / 2, (screenSide - referenceSize.height) / 2, referenceSize.width, referenceSize.height);
_transitionWrapperView.frame = _containerView.frame;
UIEdgeInsets screenEdges = UIEdgeInsetsMake((screenSide - referenceSize.height) / 2, (screenSide - referenceSize.width) / 2, (screenSide + referenceSize.height) / 2, (screenSide + referenceSize.width) / 2);
_landscapeToolbarView.interfaceOrientation = orientation;
UIEdgeInsets safeAreaInset = [self calculatedSafeAreaInset];
switch (orientation)
{
case UIInterfaceOrientationLandscapeLeft:
{
[UIView performWithoutAnimation:^
{
_landscapeToolbarView.frame = CGRectMake(screenEdges.left, screenEdges.top, TGPhotoEditorToolbarSize + safeAreaInset.left, referenceSize.height);
}];
}
break;
case UIInterfaceOrientationLandscapeRight:
{
[UIView performWithoutAnimation:^
{
_landscapeToolbarView.frame = CGRectMake(screenEdges.right - TGPhotoEditorToolbarSize - safeAreaInset.right, screenEdges.top, TGPhotoEditorToolbarSize + safeAreaInset.right, referenceSize.height);
}];
}
break;
default:
{
_landscapeToolbarView.frame = CGRectMake(_landscapeToolbarView.frame.origin.x, screenEdges.top, TGPhotoEditorToolbarSize, referenceSize.height);
}
break;
}
CGFloat portraitToolbarViewBottomEdge = screenSide;
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
portraitToolbarViewBottomEdge = screenEdges.bottom;
CGFloat previousWidth = _portraitToolbarView.frame.size.width;
_portraitToolbarView.frame = CGRectMake(screenEdges.left, portraitToolbarViewBottomEdge - TGPhotoEditorToolbarSize - safeAreaInset.bottom, referenceSize.width, TGPhotoEditorToolbarSize + safeAreaInset.bottom);
_scrubberView.frame = CGRectMake(0.0, 0.0, _portraitToolbarView.frame.size.width, _scrubberView.frame.size.height);
dispatch_async(dispatch_get_main_queue(), ^{
if (!_initializedScrubber) {
[_scrubberView layoutSubviews];
_initializedScrubber = true;
[_scrubberView reloadData];
[_scrubberView resetToStart];
if (_isPlaying)
[_scrubberView _updateScrubberAnimationsAndResetCurrentPosition:true];
} else {
if (previousWidth != _portraitToolbarView.frame.size.width)
[_scrubberView reloadThumbnails];
}
});
}
- (void)_setScreenImage:(UIImage *)screenImage
{
_screenImage = screenImage;
if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]])
[(TGPhotoAvatarPreviewController *)_currentTabController setSnapshotImage:screenImage];
}
- (void)_finishedTransitionIn
{
_switchingTab = false;
if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]])
[(TGPhotoAvatarPreviewController *)_currentTabController _finishedTransitionIn];
}
- (CGFloat)toolbarLandscapeSize
{
return TGPhotoEditorToolbarSize;
}
- (UIView *)transitionWrapperView
{
return _transitionWrapperView;
}
- (void)layoutProgressView {
if (_progressView.superview == nil)
[_containerView addSubview:_progressView];
CGSize referenceSize = [self referenceViewSize];
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:0.0 hasOnScreenNavigation:self.hasOnScreenNavigation];
_progressView.frame = (CGRect){{CGFloor(CGRectGetMidX(containerFrame) - _progressView.frame.size.width / 2.0f), CGFloor(CGRectGetMidY(containerFrame) - _progressView.frame.size.height / 2.0f)}, _progressView.frame.size};
}
- (void)setProgressVisible:(bool)progressVisible value:(CGFloat)value animated:(bool)animated
{
_progressVisible = progressVisible;
if (progressVisible)
{
[self layoutProgressView];
_progressView.alpha = 1.0f;
}
else if (_progressView.superview != nil)
{
if (animated)
{
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^
{
_progressView.alpha = 0.0f;
} completion:^(BOOL finished)
{
if (finished)
[_progressView removeFromSuperview];
}];
}
else {
[_progressView removeFromSuperview];
}
}
[_progressView setProgress:value cancelEnabled:false animated:animated];
}
- (void)setInfoString:(NSString *)string
{
[_portraitToolbarView setInfoString:string];
[_landscapeToolbarView setInfoString:string];
}
- (void)detectFaces
{
if (_faceDetectorDisposable == nil)
_faceDetectorDisposable = [[SMetaDisposable alloc] init];
id<TGMediaEditableItem> item = _item;
CGSize originalSize = _photoEditor.originalSize;
if (self.requestOriginalScreenSizeImage == nil)
return;
SSignal *cachedFaces = self.editingContext != nil ? [self.editingContext facesForItem:item] : [SSignal single:nil];
SSignal *cachedSignal = [cachedFaces mapToSignal:^SSignal *(id result)
{
if (result == nil)
return [SSignal fail:nil];
return [SSignal single:result];
}];
SSignal *imageSignal = self.requestOriginalScreenSizeImage(item, 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]];
}];
__weak TGPhotoEditorController *weakSelf = self;
[_faceDetectorDisposable setDisposable:[[[cachedSignal catch:^SSignal *(__unused id error)
{
return detectSignal;
}] deliverOn:[SQueue mainQueue]] startStrictWithNext:^(NSArray *next)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf.editingContext setFaces:next forItem:item];
if (next.count == 0)
return;
strongSelf->_faces = next;
} file:__FILE_NAME__ line:__LINE__]];
}
+ (TGPhotoEditorTab)defaultTabsForAvatarIntent:(bool)hasStickers
{
TGPhotoEditorTab avatarTabs = TGPhotoEditorCropTab | TGPhotoEditorToolsTab;
if (hasStickers) {
avatarTabs |= TGPhotoEditorPaintTab;
}
return avatarTabs;
}
- (void)setPlayButtonHidden:(bool)hidden animated:(bool)animated
{
if (!hidden) {
[_progressView setPlay];
[self layoutProgressView];
}
if (animated)
{
_progressView.hidden = false;
_progressView.alpha = 0.0f;
[UIView animateWithDuration:0.15f animations:^
{
_progressView.alpha = hidden ? 0.0f : 1.0f;
} completion:^(BOOL finished)
{
if (finished)
_progressView.hidden = hidden;
}];
}
else
{
_progressView.alpha = hidden ? 0.0f : 1.0f;
_progressView.hidden = hidden;
}
}
#pragma mark - Video Scrubber Data Source & Delegate
#pragma mark Scrubbing
- (id<TGMediaEditableItem>)item {
return _item;
}
- (NSTimeInterval)videoScrubberDuration:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber
{
return self.item.originalDuration;
}
- (CGFloat)videoScrubberThumbnailAspectRatio:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber
{
return 1.0f;
}
- (void)videoScrubberDidBeginScrubbing:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber
{
[self stopVideoPlayback:false];
[self setPlayButtonHidden:true animated:false];
TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController;
if (![previewController isKindOfClass:[TGPhotoAvatarPreviewController class]])
return;
[previewController beginScrubbing:true];
}
- (void)resetDotImage {
UIView *snapshotView = nil;
UIView *dotSnapshotView = nil;
if (_dotImageView.image != nil) {
dotSnapshotView = [_dotMarkerView snapshotViewAfterScreenUpdates:false];
dotSnapshotView.frame = _dotMarkerView.frame;
[_dotMarkerView.superview addSubview:dotSnapshotView];
snapshotView = [_dotImageView snapshotViewAfterScreenUpdates:false];
snapshotView.frame = [_dotImageView.superview convertRect:_dotImageView.frame toView:_dotMarkerView.superview];
[_dotMarkerView.superview addSubview:snapshotView];
}
if (snapshotView != nil) {
[UIView animateWithDuration:0.15 animations:^{
snapshotView.center = _dotMarkerView.center;
snapshotView.transform = CGAffineTransformMakeScale(0.05, 0.05);
snapshotView.alpha = 0.0f;
dotSnapshotView.transform = CGAffineTransformMakeScale(0.3, 0.3);
dotSnapshotView.alpha = 0.0f;
} completion:^(BOOL finished) {
[snapshotView removeFromSuperview];
[dotSnapshotView removeFromSuperview];
}];
}
_dotImageView.image = nil;
_dotMarkerView.hidden = true;
}
- (void)updateDotImage:(bool)animated {
AVPlayer *player = _player;
if (player == nil) {
return;
}
id<TGMediaEditAdjustments> adjustments = [_photoEditor exportAdjustments];
[[SQueue concurrentDefaultQueue] dispatch:^{
AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:player.currentItem.asset];
generator.appliesPreferredTrackTransform = true;
generator.maximumSize = CGSizeMake(160.0f, 160.0f);
generator.requestedTimeToleranceAfter = kCMTimeZero;
generator.requestedTimeToleranceBefore = kCMTimeZero;
CGImageRef imageRef = [generator copyCGImageAtTime:player.currentItem.currentTime actualTime:NULL error:NULL];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef);
if (adjustments.toolsApplied) {
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];
image = editor.currentResultImage;
}
TGDispatchOnMainThread(^{
if (animated) {
UIView *snapshotView = nil;
UIView *dotSnapshotView = nil;
if (_dotImageView.image != nil) {
dotSnapshotView = [_dotMarkerView snapshotViewAfterScreenUpdates:false];
dotSnapshotView.frame = _dotMarkerView.frame;
[_dotMarkerView.superview addSubview:dotSnapshotView];
snapshotView = [_dotImageView snapshotViewAfterScreenUpdates:false];
snapshotView.frame = [_dotImageView.superview convertRect:_dotImageView.frame toView:_dotMarkerView.superview];
[_dotMarkerView.superview addSubview:snapshotView];
}
if (snapshotView != nil) {
[UIView animateWithDuration:0.15 animations:^{
snapshotView.center = _dotMarkerView.center;
snapshotView.transform = CGAffineTransformMakeScale(0.05, 0.05);
snapshotView.alpha = 0.0f;
dotSnapshotView.transform = CGAffineTransformMakeScale(0.3, 0.3);
dotSnapshotView.alpha = 0.0f;
} completion:^(BOOL finished) {
[snapshotView removeFromSuperview];
[dotSnapshotView removeFromSuperview];
}];
}
_dotMarkerView.hidden = false;
_dotImageView.image = image;
_dotImageView.cropRect = _photoEditor.cropRect;
_dotImageView.cropOrientation = _photoEditor.cropOrientation;
_dotImageView.cropMirrored = _photoEditor.cropMirrored;
[_dotImageView updateCropping];
[_scrubberView addSubview:_dotMarkerView];
_dotMarkerView.center = CGPointMake([_scrubberView scrubberPositionForPosition:_dotPosition].x + 7.0, 9.5);
_dotMarkerView.transform = CGAffineTransformMakeScale(0.3, 0.3);
_dotMarkerView.alpha = 0.0;
[UIView animateWithDuration:0.3 animations:^{
_dotMarkerView.transform = CGAffineTransformIdentity;
_dotMarkerView.alpha = 1.0;
}];
UIEdgeInsets screenEdges = [self screenEdges];
CGSize referenceSize = [self referenceViewSize];
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:self.effectiveOrientation panelSize:0.0 hasOnScreenNavigation:self.hasOnScreenNavigation];
containerFrame.origin.x += screenEdges.left;
containerFrame.origin.y += screenEdges.top;
CGFloat scale = (containerFrame.size.width - [TGPhotoAvatarCropView areaInsetSize].width * 2.0) / 160.0;
_dotImageView.center = CGPointMake(CGRectGetMidX(containerFrame), CGRectGetMidY(containerFrame));
_dotImageView.transform = CGAffineTransformMakeScale(scale, scale);
CGPoint targetCenter = [_dotMarkerView.superview convertPoint:_dotMarkerView.center toView:_wrapperView];
targetCenter.y -= 27.0;
[UIView animateWithDuration:0.4 delay:0.0 usingSpringWithDamping:1.1 initialSpringVelocity:0.1 options:kNilOptions animations:^{
_dotImageView.center = targetCenter;
_dotImageView.transform = CGAffineTransformMakeScale(0.225, 0.225);
} completion:^(BOOL finished) {
}];
} else {
if (_dotImageView.image != nil) {
[_scrubberView addSubview:_dotMarkerView];
UIView *snapshotView;
if (_dotImageView.image != nil) {
_dotImageSnapshotView = [_dotImageView snapshotViewAfterScreenUpdates:false];
snapshotView.frame = _dotImageView.bounds;
[_dotImageView addSubview:snapshotView];
}
_dotMarkerView.hidden = false;
_dotImageView.image = image;
_dotImageView.cropRect = _photoEditor.cropRect;
_dotImageView.cropOrientation = _photoEditor.cropOrientation;
_dotImageView.cropMirrored = _photoEditor.cropMirrored;
[_dotImageView updateCropping];
}
}
});
}];
}
- (void)videoScrubberDidEndScrubbing:(TGMediaPickerGalleryVideoScrubber *)videoScrubber
{
__weak TGPhotoEditorController *weakSelf = self;
TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController;
if (![previewController isKindOfClass:[TGPhotoAvatarPreviewController class]])
return;
_dotPosition = videoScrubber.value;
[previewController endScrubbing:true completion:^bool{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return false;
return !strongSelf->_scrubberView.isScrubbing;
}];
TGDispatchAfter(0.16, dispatch_get_main_queue(), ^{
[self updateDotImage:true];
});
}
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)videoScrubber valueDidChange:(NSTimeInterval)position
{
[self seekVideo:position];
}
#pragma mark Trimming
- (bool)hasTrimming
{
return _scrubberView.hasTrimming;
}
- (CMTimeRange)trimRange
{
return CMTimeRangeMake(CMTimeMakeWithSeconds(self.trimStartValue , NSEC_PER_SEC), CMTimeMakeWithSeconds((self.trimEndValue - self.trimStartValue), NSEC_PER_SEC));
}
- (void)videoScrubberDidBeginEditing:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber
{
[self stopVideoPlayback:false];
[self setPlayButtonHidden:true animated:false];
}
- (void)videoScrubberDidEndEditing:(TGMediaPickerGalleryVideoScrubber *)videoScrubber
{
if (_resetDotPosition) {
_dotPosition = videoScrubber.trimStartValue;
_resetDotPosition = false;
}
[self setVideoEndTime:videoScrubber.trimEndValue];
[videoScrubber resetToStart];
[self startVideoPlayback:true];
[self setPlayButtonHidden:true animated:false];
_chaseStart = false;
}
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)videoScrubber editingStartValueDidChange:(NSTimeInterval)startValue
{
if (startValue > _dotPosition || videoScrubber.trimEndValue < _dotPosition) {
_resetDotPosition = true;
[self resetDotImage];
}
if (!_chaseStart) {
_chaseStart = true;
[_fullEntitiesView resetToStart];
}
[self seekVideo:startValue];
}
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber editingEndValueDidChange:(NSTimeInterval)endValue
{
if (endValue < _dotPosition || videoScrubber.trimStartValue > _dotPosition) {
_resetDotPosition = true;
[self resetDotImage];
}
[self seekVideo:endValue];
}
#pragma mark Thumbnails
- (NSArray *)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)videoScrubber evenlySpacedTimestamps:(NSInteger)count startingAt:(NSTimeInterval)startTimestamp endingAt:(NSTimeInterval)endTimestamp
{
if (endTimestamp < startTimestamp)
return nil;
if (count == 0)
return nil;
NSTimeInterval duration = [self videoScrubberDuration:videoScrubber];
if (endTimestamp > duration)
endTimestamp = duration;
NSTimeInterval interval = (endTimestamp - startTimestamp) / count;
NSMutableArray *timestamps = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < count; i++)
[timestamps addObject:@(startTimestamp + i * interval)];
return timestamps;
}
- (NSArray *)_placeholderThumbnails:(NSArray *)timestamps {
NSMutableArray *thumbnails = [[NSMutableArray alloc] init];
UIImage *blurredImage = TGBlurredRectangularImage(_screenImage, true, _screenImage.size, _screenImage.size, NULL, nil);
for (__unused NSNumber *value in timestamps) {
if (thumbnails.count == 0)
[thumbnails addObject:_screenImage];
else
[thumbnails addObject:blurredImage];
}
return thumbnails;
}
- (void)videoScrubber:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber requestThumbnailImagesForTimestamps:(NSArray *)timestamps size:(CGSize)size isSummaryThumbnails:(bool)isSummaryThumbnails
{
if (timestamps.count == 0)
return;
id<TGMediaEditAdjustments> adjustments = [_photoEditor exportAdjustments];
__weak TGPhotoEditorController *weakSelf = self;
SSignal *thumbnailsSignal = nil;
if (_cachedThumbnails != nil) {
thumbnailsSignal = [SSignal single:_cachedThumbnails];
} else if ([self.item isKindOfClass:[TGMediaAsset class]]) {
thumbnailsSignal = [[SSignal single:[self _placeholderThumbnails:timestamps]] then:[[TGMediaAssetImageSignals videoThumbnailsForAsset:(TGMediaAsset *)self.item size:size timestamps:timestamps] onNext:^(NSArray *images) {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_cachedThumbnails == nil)
strongSelf->_cachedThumbnails = images;
}]];
} else if ([self.item isKindOfClass:[TGCameraCapturedVideo class]]) {
thumbnailsSignal = [[((TGCameraCapturedVideo *)self.item).avAsset takeLast] mapToSignal:^SSignal *(AVAsset *avAsset) {
return [[SSignal single:[self _placeholderThumbnails:timestamps]] then:[[TGMediaAssetImageSignals videoThumbnailsForAVAsset:avAsset size:size timestamps:timestamps] onNext:^(NSArray *images) {
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf->_cachedThumbnails == nil)
strongSelf->_cachedThumbnails = images;
}]];
}];
}
_requestingThumbnails = true;
[_thumbnailsDisposable setDisposable:[[[thumbnailsSignal map:^NSArray *(NSArray *images) {
if (adjustments.toolsApplied) {
NSMutableArray *editedImages = [[NSMutableArray alloc] init];
PGPhotoEditor *editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:false enableStickers:true];
editor.standalone = true;
for (UIImage *image in images) {
[editor setImage:image forCropRect:adjustments.cropRect cropRotation:0.0 cropOrientation:adjustments.cropOrientation cropMirrored:adjustments.cropMirrored fullSize:false];
UIImage *resultImage = editor.currentResultImage;
if (resultImage != nil) {
[editedImages addObject:resultImage];
} else {
[editedImages addObject:image];
}
}
return editedImages;
} else {
return images;
}
}] deliverOn:[SQueue mainQueue]] startStrictWithNext:^(NSArray *images)
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[images enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger index, __unused BOOL *stop)
{
if (index < timestamps.count)
[strongSelf->_scrubberView setThumbnailImage:image forTimestamp:[timestamps[index] doubleValue] index:index isSummaryThubmnail:isSummaryThumbnails last:index == (images.count - 1)];
}];
if (strongSelf->_dotImageSnapshotView != nil) {
[UIView animateWithDuration:0.2 animations:^{
strongSelf->_dotImageSnapshotView.alpha = 0.0f;
} completion:^(BOOL finished) {
[strongSelf->_dotImageSnapshotView removeFromSuperview];
strongSelf->_dotImageSnapshotView = nil;
}];
}
} completed:^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf != nil)
strongSelf->_requestingThumbnails = false;
} file:__FILE_NAME__ line:__LINE__]];
}
- (void)videoScrubberDidFinishRequestingThumbnails:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber
{
_requestingThumbnails = false;
}
- (void)videoScrubberDidCancelRequestingThumbnails:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber
{
_requestingThumbnails = false;
}
- (CGSize)videoScrubberOriginalSize:(TGMediaPickerGalleryVideoScrubber *)__unused videoScrubber cropRect:(CGRect *)cropRect cropOrientation:(UIImageOrientation *)cropOrientation cropMirrored:(bool *)cropMirrored
{
id<TGMediaEditAdjustments> adjustments = [_photoEditor exportAdjustments];
if (cropRect != NULL)
*cropRect = (adjustments != nil) ? adjustments.cropRect : CGRectMake(0, 0, self.item.originalSize.width, self.item.originalSize.height);
if (cropOrientation != NULL)
*cropOrientation = (adjustments != nil) ? adjustments.cropOrientation : UIImageOrientationUp;
if (cropMirrored != NULL)
*cropMirrored = adjustments.cropMirrored;
return self.item.originalSize;
}
@end