#import "TGPhotoEditorController.h" #import "LegacyComponentsInternal.h" #import #import #import #import "TGPhotoEditorInterfaceAssets.h" #import #import #import #import "TGProgressWindow.h" #import "PGPhotoEditor.h" #import "PGPhotoEditorView.h" #import "TGPaintFaceDetector.h" #import #import #import #import #import "TGPhotoToolbarView.h" #import "TGPhotoEditorPreviewView.h" #import #import #import #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 #import "TGCameraCapturedVideo.h" @interface TGPhotoEditorController () { 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 *_fullEntitiesView; UIImageView *_fullPaintingView; PGPhotoEditor *_photoEditor; SQueue *_queue; TGPhotoEditorControllerIntent _intent; id _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 _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 _context; } @property (nonatomic, weak) UIImage *fullSizeImage; @end @implementation TGPhotoEditorController - (instancetype)initWithContext:(id)context item:(id)item intent:(TGPhotoEditorControllerIntent)intent adjustments:(id)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 stickersContext:nil]; [_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 stickersContext:nil]; [_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) cover: false]; _scrubberView.minimumLength = 3.0; _scrubberView.layer.allowsGroupOpacity = true; _scrubberView.hasDotPicker = true; _scrubberView.dataSource = self; _scrubberView.delegate = self; _scrubberView.clipsToBounds = false; } [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)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(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(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, 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:false]; [_portraitToolbarView setCenterButtonsHidden:false animated:false]; [_landscapeToolbarView setAllButtonsHidden:false animated:false]; } 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 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 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 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)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 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 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 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