From 7741978b7ea9e10a4dd4518ca3f90dca21b1759d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 23 May 2020 13:26:53 +0300 Subject: [PATCH 1/8] Video Editing --- Telegram/BUILD | 1 + .../Sources/AnimatedStickerNode.swift | 34 +- .../Sources/AnimationRenderer.swift | 2 +- submodules/LegacyComponents/BUILD | 7 + .../LegacyImages.xcassets/Contents.json | 6 + .../Editor/AddSticker.imageset/Contents.json | 12 + .../ic_editor_addsticker.pdf | Bin 0 -> 4945 bytes .../Editor/AddText.imageset/Contents.json | 12 + .../AddText.imageset/ic_editor_addtext.pdf | Bin 0 -> 4392 bytes .../Editor/Adjustments.imageset/Contents.json | 12 + .../Adjustments.imageset/ic_editor_tools.pdf | Bin 0 -> 4161 bytes .../Editor/AspectRatio.imageset/Contents.json | 12 + .../AspectRatio.imageset/ic_editor_frame.pdf | Bin 0 -> 4294 bytes .../Editor/Blur.imageset/Contents.json | 12 + .../Editor/Blur.imageset/ic_editor_blur.pdf | Bin 0 -> 4072 bytes .../Editor/Brush.imageset/Contents.json | 12 + .../Brush.imageset/ic_editor_brushtype.pdf | Bin 0 -> 4507 bytes .../Editor/Cancel.imageset/Contents.json | 12 + .../Cancel.imageset/ic_editor_close (2).pdf | Bin 0 -> 4146 bytes .../Editor/Commit.imageset/Contents.json | 12 + .../Commit.imageset/ic_editor_check (2).pdf | Bin 0 -> 4104 bytes .../Editor/Contents.json | 9 + .../Editor/Crop.imageset/Contents.json | 12 + .../Editor/Crop.imageset/ic_editor_crop.pdf | Bin 0 -> 4166 bytes .../Editor/Curves.imageset/Contents.json | 12 + .../Curves.imageset/ic_editor_curves.pdf | Bin 0 -> 4441 bytes .../Editor/Drawing.imageset/Contents.json | 12 + .../Drawing.imageset/ic_editor_brush.pdf | Bin 0 -> 4847 bytes .../Editor/Eraser.imageset/Contents.json | 12 + .../Eraser.imageset/ic_editor_eracer.pdf | Bin 0 -> 4275 bytes .../Editor/Eyedropper.imageset/Contents.json | 12 + .../ic_editor_eyedropper.pdf | Bin 0 -> 4440 bytes .../Editor/Flip.imageset/Contents.json | 12 + .../Editor/Flip.imageset/ic_editor_flip.pdf | Bin 0 -> 4156 bytes .../Editor/Font.imageset/Contents.json | 12 + .../Editor/Font.imageset/ic_editor_font.pdf | Bin 0 -> 4331 bytes .../Editor/Mute.imageset/Contents.json | 12 + .../Editor/Mute.imageset/ic_editor_muted.pdf | Bin 0 -> 4945 bytes .../Editor/Play.imageset/Contents.json | 12 + .../Editor/Play.imageset/ic_editor_play.pdf | Bin 0 -> 3993 bytes .../Editor/Recipient.imageset/Contents.json | 12 + .../Editor/Recipient.imageset/send.pdf | Bin 0 -> 4113 bytes .../Editor/Rotate.imageset/Contents.json | 12 + .../Rotate.imageset/ic_editor_rotate.pdf | Bin 0 -> 4457 bytes .../Editor/Tint.imageset/Contents.json | 12 + .../Editor/Tint.imageset/ic_editor_tint.pdf | Bin 0 -> 4063 bytes .../Editor/Undo.imageset/Contents.json | 12 + .../Editor/Undo.imageset/ic_editor_undo.pdf | Bin 0 -> 4116 bytes .../Editor/Unmute.imageset/Contents.json | 12 + .../Unmute.imageset/ic_editor_unmuted.pdf | Bin 0 -> 4736 bytes .../LegacyComponents/LegacyComponents.h | 2 + .../TGAttachmentCarouselItemView.h | 2 + .../TGMediaAssetsController.h | 3 + .../LegacyComponents/TGMediaEditingContext.h | 3 +- .../TGMediaPickerController.h | 3 + .../TGMediaPickerGalleryModel.h | 3 + .../TGMediaPickerModernGalleryMixin.h | 6 +- .../LegacyComponents/TGMediaVideoConverter.h | 5 +- .../TGModernGalleryZoomableScrollView.h | 2 + .../LegacyComponents/TGPaintingData.h | 6 +- .../TGPhotoEditorController.h | 3 + .../TGPhotoEditorInterfaceAssets.h | 1 - .../LegacyComponents/TGPhotoPaintEntity.h | 1 + .../TGPhotoPaintStickerEntity.h | 4 +- .../TGPhotoPaintStickersContext.h | 25 + .../TGPhotoPaintTextEntity.h | 7 +- .../LegacyComponents/TGPhotoToolbarView.h | 6 +- .../Sources/GPUImageFramebuffer.h | 2 + .../Sources/GPUImageFramebuffer.m | 25 +- .../Sources/GPUImageFramebufferCache.m | 2 - .../LegacyComponents/Sources/GPUImageOutput.h | 33 - .../Sources/GPUImageTextureInput.h | 16 + .../Sources/GPUImageTextureInput.m | 94 ++ .../Sources/GPUImageTextureOutput.h | 24 + .../Sources/GPUImageTextureOutput.m | 91 ++ .../Sources/LegacyComponentsInternal.m | 6 +- .../LegacyComponents/Sources/PGPhotoEditor.h | 2 + .../LegacyComponents/Sources/PGPhotoEditor.m | 70 +- .../LegacyComponents/Sources/PGVideoMovie.m | 23 +- .../Sources/TGAttachmentCarouselItemView.m | 2 +- .../Sources/TGCameraPhotoPreviewController.m | 2 +- .../Sources/TGClipboardMenu.m | 1 + submodules/LegacyComponents/Sources/TGFont.mm | 4 +- .../Sources/TGMediaAssetsController.m | 13 +- .../Sources/TGMediaAssetsPickerController.m | 2 +- .../Sources/TGMediaEditingContext.m | 44 +- .../TGMediaPickerGalleryInterfaceView.m | 2 +- .../Sources/TGMediaPickerGalleryModel.m | 1 + .../TGMediaPickerGalleryVideoItemView.m | 6 +- .../Sources/TGMediaPickerModernGalleryMixin.m | 11 +- .../Sources/TGMediaVideoConverter.m | 394 +++++--- .../Sources/TGMessageImageViewOverlayView.m | 15 +- .../Sources/TGModernGalleryZoomableItemView.m | 2 +- .../TGModernGalleryZoomableScrollView.m | 33 +- .../LegacyComponents/Sources/TGPaintBrush.m | 2 +- .../LegacyComponents/Sources/TGPaintCanvas.m | 32 +- .../LegacyComponents/Sources/TGPaintInput.m | 10 +- .../Sources/TGPaintPanGestureRecognizer.m | 6 +- .../LegacyComponents/Sources/TGPaintRender.m | 3 +- .../LegacyComponents/Sources/TGPainting.h | 1 + .../LegacyComponents/Sources/TGPainting.m | 9 + .../LegacyComponents/Sources/TGPaintingData.m | 33 +- .../Sources/TGPhotoEditorController.m | 36 +- .../Sources/TGPhotoEditorInterfaceAssets.m | 55 +- .../Sources/TGPhotoEditorPreviewView.m | 2 +- .../Sources/TGPhotoEntitiesContainerView.h | 2 +- .../Sources/TGPhotoEntitiesContainerView.m | 18 +- .../Sources/TGPhotoPaintActionsView.m | 3 +- .../Sources/TGPhotoPaintController.h | 4 + .../Sources/TGPhotoPaintController.m | 456 +++++---- .../Sources/TGPhotoPaintSettingsView.m | 6 +- .../Sources/TGPhotoPaintStickerEntity.m | 10 +- .../Sources/TGPhotoPaintTextEntity.m | 7 + .../Sources/TGPhotoQualityController.m | 2 +- .../Sources/TGPhotoStickerEntityView.h | 3 +- .../Sources/TGPhotoStickerEntityView.m | 82 +- .../Sources/TGPhotoTextEntityView.h | 1 + .../Sources/TGPhotoTextEntityView.m | 17 + .../Sources/TGPhotoToolbarView.m | 5 +- .../Sources/TGVideoEditAdjustments.m | 99 +- submodules/LegacyMediaPickerUI/BUILD | 3 + .../Sources/LegacyAttachmentMenu.swift | 12 +- .../Sources/LegacyPaintStickerView.swift | 118 +++ .../Sources/LegacyPaintStickersContext.swift | 316 ++++++ .../ThemeAutoNightSettingsController.swift | 22 +- .../Sources/MessageContentKind.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 10 +- .../Sources/ChatMediaInputNode.swift | 33 +- .../Sources/DrawingStickersScreen.swift | 901 ++++++++++++++++++ .../Sources/FetchVideoMediaResource.swift | 53 +- .../TelegramAccountAuxiliaryMethods.swift | 4 +- 131 files changed, 3072 insertions(+), 631 deletions(-) create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/ic_editor_addsticker.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddText.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddText.imageset/ic_editor_addtext.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/ic_editor_tools.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/AspectRatio.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/AspectRatio.imageset/ic_editor_frame.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/ic_editor_blur.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/ic_editor_brushtype.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Cancel.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Cancel.imageset/ic_editor_close (2).pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/ic_editor_check (2).pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Crop.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Crop.imageset/ic_editor_crop.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Curves.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Curves.imageset/ic_editor_curves.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/ic_editor_brush.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eraser.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eraser.imageset/ic_editor_eracer.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/ic_editor_eyedropper.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Flip.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Flip.imageset/ic_editor_flip.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Font.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Font.imageset/ic_editor_font.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Mute.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Mute.imageset/ic_editor_muted.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/ic_editor_play.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/send.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Rotate.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Rotate.imageset/ic_editor_rotate.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Tint.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Tint.imageset/ic_editor_tint.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Undo.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Undo.imageset/ic_editor_undo.pdf create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/Contents.json create mode 100644 submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/ic_editor_unmuted.pdf create mode 100644 submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h rename submodules/LegacyComponents/{Sources => PublicHeaders/LegacyComponents}/TGPhotoPaintTextEntity.h (85%) create mode 100755 submodules/LegacyComponents/Sources/GPUImageTextureInput.h create mode 100755 submodules/LegacyComponents/Sources/GPUImageTextureInput.m create mode 100755 submodules/LegacyComponents/Sources/GPUImageTextureOutput.h create mode 100755 submodules/LegacyComponents/Sources/GPUImageTextureOutput.m create mode 100644 submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickerView.swift create mode 100644 submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift create mode 100644 submodules/TelegramUI/Sources/DrawingStickersScreen.swift diff --git a/Telegram/BUILD b/Telegram/BUILD index 61efb4a8e0..26d56c83f1 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -156,6 +156,7 @@ swift_library( ":AppIntentVocabularyResources", ":InfoPlistStringResources", "//submodules/LegacyComponents:LegacyComponentsResources", + "//submodules/LegacyComponents:LegacyComponentsAssets", "//submodules/OverlayStatusController:OverlayStatusControllerResources", "//submodules/PasswordSetupUI:PasswordSetupUIResources", "//submodules/PasswordSetupUI:PasswordSetupUIAssets", diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift index f7cb2ab2b2..84445b572e 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerNode.swift @@ -60,12 +60,12 @@ public enum AnimatedStickerPlaybackMode { case still(AnimatedStickerPlaybackPosition) } -private final class AnimatedStickerFrame { - let data: Data - let type: AnimationRendererFrameType - let width: Int - let height: Int - let bytesPerRow: Int +public final class AnimatedStickerFrame { + public let data: Data + public let type: AnimationRendererFrameType + public let width: Int + public let height: Int + public let bytesPerRow: Int let index: Int let isLastFrame: Bool @@ -80,7 +80,7 @@ private final class AnimatedStickerFrame { } } -private protocol AnimatedStickerFrameSource: class { +public protocol AnimatedStickerFrameSource: class { var frameRate: Int { get } var frameCount: Int { get } @@ -97,7 +97,7 @@ private final class AnimatedStickerFrameSourceWrapper { } @available(iOS 9.0, *) -private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource { +public final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource { private let queue: Queue private var data: Data private var dataComplete: Bool @@ -107,15 +107,15 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource let width: Int let bytesPerRow: Int let height: Int - let frameRate: Int - let frameCount: Int + public let frameRate: Int + public let frameCount: Int private var frameIndex: Int private let initialOffset: Int private var offset: Int var decodeBuffer: Data var frameBuffer: Data - init?(queue: Queue, data: Data, complete: Bool, notifyUpdated: @escaping () -> Void) { + public init?(queue: Queue, data: Data, complete: Bool, notifyUpdated: @escaping () -> Void) { self.queue = queue self.data = data self.dataComplete = complete @@ -179,7 +179,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource assert(self.queue.isCurrent()) } - func takeFrame() -> AnimatedStickerFrame? { + public func takeFrame() -> AnimatedStickerFrame? { var frameData: Data? var isLastFrame = false @@ -259,7 +259,7 @@ private final class AnimatedStickerCachedFrameSource: AnimatedStickerFrameSource self.dataComplete = complete } - func skipToEnd() { + public func skipToEnd() { } } @@ -310,13 +310,13 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource } } -private final class AnimatedStickerFrameQueue { +public final class AnimatedStickerFrameQueue { private let queue: Queue private let length: Int private let source: AnimatedStickerFrameSource private var frames: [AnimatedStickerFrame] = [] - init(queue: Queue, length: Int, source: AnimatedStickerFrameSource) { + public init(queue: Queue, length: Int, source: AnimatedStickerFrameSource) { self.queue = queue self.length = length self.source = source @@ -326,7 +326,7 @@ private final class AnimatedStickerFrameQueue { assert(self.queue.isCurrent()) } - func take() -> AnimatedStickerFrame? { + public func take() -> AnimatedStickerFrame? { if self.frames.isEmpty { if let frame = self.source.takeFrame() { self.frames.append(frame) @@ -340,7 +340,7 @@ private final class AnimatedStickerFrameQueue { } } - func generateFramesIfNeeded() { + public func generateFramesIfNeeded() { if self.frames.isEmpty { if let frame = self.source.takeFrame() { self.frames.append(frame) diff --git a/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift b/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift index 1c171dac83..87ef1f37aa 100644 --- a/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift +++ b/submodules/AnimatedStickerNode/Sources/AnimationRenderer.swift @@ -2,7 +2,7 @@ import Foundation import SwiftSignalKit import AsyncDisplayKit -enum AnimationRendererFrameType { +public enum AnimationRendererFrameType { case argb case yuva } diff --git a/submodules/LegacyComponents/BUILD b/submodules/LegacyComponents/BUILD index 074269a828..81a3745ef8 100644 --- a/submodules/LegacyComponents/BUILD +++ b/submodules/LegacyComponents/BUILD @@ -7,6 +7,13 @@ filegroup( visibility = ["//visibility:public"], ) +filegroup( + name = "LegacyComponentsAssets", + srcs = glob(["LegacyImages.xcassets/**"]), + visibility = ["//visibility:public"], +) + + objc_library( name = "LegacyComponents", enable_modules = True, diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/Contents.json new file mode 100644 index 0000000000..1c0f9b65ca --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_addsticker.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/ic_editor_addsticker.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/AddSticker.imageset/ic_editor_addsticker.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4a0e3ff1bd1eb4b108da74e024b6b7c3aa7287b0 GIT binary patch literal 4945 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®Qqx!7M`1m0!45A# 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 TYx!fMMWHY;Fa)BER001FRezw; literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/Contents.json new file mode 100644 index 0000000000..e5bcd2364e --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_tools.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/ic_editor_tools.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Adjustments.imageset/ic_editor_tools.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e7db29f833dd01a70bf9c62127a3eb8508a68d96 GIT binary patch literal 4161 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 b|6B6YKtvLKFMljF3=UNWi;L?S>4X0df+J|F literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/Contents.json new file mode 100644 index 0000000000..a6c3c0e5d8 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_blur.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/ic_editor_blur.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Blur.imageset/ic_editor_blur.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0dd29d302193b8462183ae87dd12679efafff936 GIT binary patch literal 4072 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 DklO?y literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/Contents.json new file mode 100644 index 0000000000..2bfc036bdc --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_brushtype.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/ic_editor_brushtype.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Brush.imageset/ic_editor_brushtype.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d27572a0bb29f7f8200a6973016aa7c67744b41a GIT binary patch literal 4507 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 jsDP1|by9)+?~tDc@*&b|`QxGyaMsOJ& literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/Contents.json new file mode 100644 index 0000000000..efa7620e6b --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_check (2).pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/ic_editor_check (2).pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Commit.imageset/ic_editor_check (2).pdf new file mode 100644 index 0000000000000000000000000000000000000000..b3af47d0bd794e62a86635bbdaa06e75d1c4893f GIT binary patch literal 4104 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 QMIfRgU`|dYO=a-^03T_^>i_@% literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/Contents.json new file mode 100644 index 0000000000..3076fdf67b --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_brush.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/ic_editor_brush.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Drawing.imageset/ic_editor_brush.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f79859caaa37c9684f448f7f9c075d1ecef11a2e GIT binary patch literal 4847 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 G!T$k*rd?nF literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/Contents.json new file mode 100644 index 0000000000..2031d3ca22 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_eyedropper.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/ic_editor_eyedropper.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Eyedropper.imageset/ic_editor_eyedropper.pdf new file mode 100644 index 0000000000000000000000000000000000000000..249385c9ad6c437a1d6259bc41a0049b904a52c4 GIT binary patch literal 4440 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 KF0QSQ0{shJXTp>K literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/Contents.json new file mode 100644 index 0000000000..93ee70ef42 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_play.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/ic_editor_play.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Play.imageset/ic_editor_play.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e068b7fafe5f26cb7949d9b07141965e74526dde GIT binary patch literal 3993 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 bcL}5Y`rbSb2owwj78ln-YlHs>yx-F- literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/Contents.json new file mode 100644 index 0000000000..7d9bd740b5 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "send.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/send.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Recipient.imageset/send.pdf new file mode 100644 index 0000000000000000000000000000000000000000..246d25b72ccb1593a523ee7df0d196ac1da01c3f GIT binary patch literal 4113 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 literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/Contents.json b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/Contents.json new file mode 100644 index 0000000000..e81de9ed61 --- /dev/null +++ b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_editor_unmuted.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/ic_editor_unmuted.pdf b/submodules/LegacyComponents/LegacyImages.xcassets/Editor/Unmute.imageset/ic_editor_unmuted.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c78e550ee8001e962aeebbec87a6e316809d347b GIT binary patch literal 4736 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 IT~CAQKOizz`~Uy| literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h index d584d8d03e..54dac0dba1 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/LegacyComponents.h @@ -264,6 +264,8 @@ #import #import #import +#import +#import #import #import #import diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h index 54638ed711..0436432211 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGAttachmentCarouselItemView.h @@ -10,6 +10,7 @@ @class TGViewController; @class TGAttachmentCameraView; @protocol TGModernGalleryTransitionHostScrollView; +@protocol TGPhotoPaintStickersContext; @interface TGAttachmentCarouselCollectionView : UICollectionView @@ -22,6 +23,7 @@ @property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; @property (nonatomic, readonly) TGMediaEditingContext *editingContext; @property (nonatomic, strong) TGSuggestionContext *suggestionContext; +@property (nonatomic, strong) id stickersContext; @property (nonatomic) bool allowCaptions; @property (nonatomic) bool allowCaptionEntities; @property (nonatomic) bool inhibitDocumentCaptions; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h index 348bab118a..a70d99bb97 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaAssetsController.h @@ -9,6 +9,8 @@ @class TGMediaAssetsPickerController; @class TGViewController; +@protocol TGPhotoPaintStickersContext; + typedef enum { TGMediaAssetsControllerSendMediaIntent, @@ -49,6 +51,7 @@ typedef enum @property (nonatomic, readonly) TGMediaEditingContext *editingContext; @property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; @property (nonatomic, strong) TGSuggestionContext *suggestionContext; +@property (nonatomic, strong) id stickersContext; @property (nonatomic, assign) bool localMediaCacheEnabled; @property (nonatomic, assign) bool captionsEnabled; @property (nonatomic, assign) bool allowCaptionEntities; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h index f3b89a11fb..ebecb48fae 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaEditingContext.h @@ -81,7 +81,8 @@ - (SSignal *)timersUpdatedSignal; - (UIImage *)paintingImageForItem:(NSObject *)item; -- (bool)setPaintingData:(NSData *)data image:(UIImage *)image forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video; +- (UIImage *)stillPaintingImageForItem:(NSObject *)item; +- (bool)setPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)image forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video; - (void)clearPaintingData; - (SSignal *)facesForItem:(NSObject *)item; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h index 8cd104554b..560181ff6f 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerController.h @@ -8,6 +8,8 @@ @class TGMediaPickerSelectionGestureRecognizer; @class TGMediaAssetsPallete; +@protocol TGPhotoPaintStickersContext; + @interface TGMediaPickerController : TGViewController { TGMediaPickerLayoutMetrics *_layoutMetrics; @@ -18,6 +20,7 @@ } @property (nonatomic, strong) TGSuggestionContext *suggestionContext; +@property (nonatomic, strong) id stickersContext; @property (nonatomic, assign) bool localMediaCacheEnabled; @property (nonatomic, assign) bool captionsEnabled; @property (nonatomic, assign) bool allowCaptionEntities; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h index 40d8da8921..b65cc68ae5 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h @@ -15,6 +15,8 @@ @class TGMediaSelectionContext; @protocol TGMediaSelectableItem; +@protocol TGPhotoPaintStickersContext; + @class TGSuggestionContext; @interface TGMediaPickerGalleryModel : TGModernGalleryModel @@ -44,6 +46,7 @@ @property (nonatomic, readonly) TGMediaSelectionContext *selectionContext; @property (nonatomic, strong) TGSuggestionContext *suggestionContext; +@property (nonatomic, strong) id stickersContext; - (instancetype)initWithContext:(id)context items:(NSArray *)items focusItem:(id)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h index f35fd05706..5868cb7e5a 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerModernGalleryMixin.h @@ -11,6 +11,8 @@ @class TGMediaAssetFetchResult; @class TGMediaAssetMomentList; +@protocol TGPhotoPaintStickersContext; + @interface TGMediaPickerModernGalleryMixin : NSObject @property (nonatomic, weak, readonly) TGMediaPickerGalleryModel *galleryModel; @@ -29,9 +31,9 @@ @property (nonatomic, copy) void (^presentScheduleController)(void (^)(int32_t)); -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder; +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext; -- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder; +- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext; - (void)present; - (void)updateWithFetchResult:(TGMediaAssetFetchResult *)fetchResult; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h index aa5023b0fe..6f9446c8c7 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h @@ -12,11 +12,12 @@ @end +@protocol TGPhotoPaintEntityRenderer; @interface TGMediaVideoConverter : NSObject -+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher; -+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio; ++ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id)entityRenderer; ++ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id)entityRenderer; + (SSignal *)hashForAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments; + (NSUInteger)estimatedSizeForPreset:(TGMediaVideoConversionPreset)preset duration:(NSTimeInterval)duration hasAudio:(bool)hasAudio; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryZoomableScrollView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryZoomableScrollView.h index d3df62565a..490bff8166 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryZoomableScrollView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryZoomableScrollView.h @@ -7,4 +7,6 @@ @property (nonatomic, copy) void (^singleTapped)(); @property (nonatomic, copy) void (^doubleTapped)(CGPoint point); +- (instancetype)initWithFrame:(CGRect)frame hasDoubleTap:(bool)hasDoubleTap; + @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h index 5c6b95d5da..99c180874b 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPaintingData.h @@ -16,7 +16,11 @@ @property (nonatomic, readonly) NSData *data; @property (nonatomic, readonly) UIImage *image; -+ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager; +@property (nonatomic, readonly) UIImage *stillImage; + ++ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager; + ++ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath entities:(NSArray *)entities; + (instancetype)dataWithPaintingImagePath:(NSString *)imagePath; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h index ae25ee29ad..f3fadff59c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h @@ -11,6 +11,8 @@ @class TGSuggestionContext; @class TGPhotoEditorController; +@protocol TGPhotoPaintStickersContext; + typedef enum { TGPhotoEditorControllerGenericIntent = 0, TGPhotoEditorControllerAvatarIntent = (1 << 0), @@ -24,6 +26,7 @@ typedef enum { @property (nonatomic, strong) TGSuggestionContext *suggestionContext; @property (nonatomic, strong) TGMediaEditingContext *editingContext; +@property (nonatomic, strong) id stickersContext; @property (nonatomic, copy) UIView *(^beginTransitionIn)(CGRect *referenceFrame, UIView **parentView); @property (nonatomic, copy) void (^finishedTransitionIn)(void); diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorInterfaceAssets.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorInterfaceAssets.h index a5c2d3e8ec..7708afa843 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorInterfaceAssets.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorInterfaceAssets.h @@ -18,7 +18,6 @@ + (UIColor *)editorButtonSelectionBackgroundColor; -+ (UIImage *)captionIcon; + (UIImage *)cropIcon; + (UIImage *)toolsIcon; + (UIImage *)rotateIcon; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintEntity.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintEntity.h index fa0c9294de..02017daf79 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintEntity.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintEntity.h @@ -7,6 +7,7 @@ } @property (nonatomic, assign) NSInteger uuid; +@property (nonatomic, readonly) bool animated; @property (nonatomic, assign) CGPoint position; @property (nonatomic, assign) CGFloat angle; @property (nonatomic, assign) CGFloat scale; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickerEntity.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickerEntity.h index b3ae077796..2971ed54ed 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickerEntity.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickerEntity.h @@ -4,11 +4,11 @@ @interface TGPhotoPaintStickerEntity : TGPhotoPaintEntity -@property (nonatomic, readonly) TGDocumentMediaAttachment *document; +@property (nonatomic, readonly) NSData *document; @property (nonatomic, readonly) NSString *emoji; @property (nonatomic, readonly) CGSize baseSize; -- (instancetype)initWithDocument:(TGDocumentMediaAttachment *)document baseSize:(CGSize)baseSize; +- (instancetype)initWithDocument:(id)document baseSize:(CGSize)baseSize animated:(bool)animated; - (instancetype)initWithEmoji:(NSString *)emoji; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h new file mode 100644 index 0000000000..0fabec411e --- /dev/null +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintStickersContext.h @@ -0,0 +1,25 @@ +#import +#import +#import + +@class TGPaintingData; + +@protocol TGPhotoPaintEntityRenderer + +- (void)entitiesForTime:(CMTime)time size:(CGSize)size completion:(void(^)(NSArray *))completion; + +@end + +@protocol TGPhotoPaintStickerRenderView + +- (UIImage *)image; + +@end + +@protocol TGPhotoPaintStickersContext + +- (UIView *)stickerViewForDocument:(id)document; + +@property (nonatomic, copy) void(^presentStickersController)(void(^)(id, bool, UIView *, CGRect)); + +@end diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintTextEntity.h similarity index 85% rename from submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.h rename to submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintTextEntity.h index 4eb3ceffca..0874828b5c 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoPaintTextEntity.h @@ -1,6 +1,7 @@ #import -#import "TGPaintSwatch.h" -#import "TGPhotoPaintFont.h" + +@class TGPaintSwatch; +@class TGPhotoPaintFont; @interface TGPhotoPaintTextEntity : TGPhotoPaintEntity @@ -11,6 +12,8 @@ @property (nonatomic, assign) CGFloat maxWidth; @property (nonatomic, assign) bool stroke; +@property (nonatomic, strong) UIImage *renderImage; + - (instancetype)initWithText:(NSString *)text font:(TGPhotoPaintFont *)font swatch:(TGPaintSwatch *)swatch baseFontSize:(CGFloat)baseFontSize maxWidth:(CGFloat)maxWidth stroke:(bool)stroke; @end diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h index 87b404a2d5..015071de7d 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoToolbarView.h @@ -3,9 +3,9 @@ typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) { TGPhotoEditorNoneTab = 0, TGPhotoEditorCropTab = 1 << 0, - TGPhotoEditorStickerTab = 1 << 1, - TGPhotoEditorPaintTab = 1 << 2, - TGPhotoEditorEraserTab = 1 << 3, + TGPhotoEditorPaintTab = 1 << 1, + TGPhotoEditorEraserTab = 1 << 2, + TGPhotoEditorStickerTab = 1 << 3, TGPhotoEditorTextTab = 1 << 4, TGPhotoEditorToolsTab = 1 << 5, TGPhotoEditorRotateTab = 1 << 6, diff --git a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h index 87ffa71589..1ee363c2f6 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h +++ b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h @@ -57,4 +57,6 @@ typedef struct GPUTextureOptions { - (NSUInteger)bytesPerRow; - (GLubyte *)byteBuffer; ++ (void)setMark:(BOOL)mark; + @end diff --git a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m index 448ebf8df8..cf00a03f29 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m +++ b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m @@ -33,13 +33,18 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); #pragma mark - #pragma mark Initialization and teardown +static BOOL mark = false; ++ (void)setMark:(BOOL)mark_ { + mark = mark_; +} + - (id)initWithSize:(CGSize)framebufferSize textureOptions:(GPUTextureOptions)fboTextureOptions onlyTexture:(BOOL)onlyGenerateTexture { if (!(self = [super init])) { return nil; } - + _mark = mark; _textureOptions = fboTextureOptions; _size = framebufferSize; framebufferReferenceCount = 0; @@ -67,7 +72,7 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); { return nil; } - + _mark = mark; GPUTextureOptions defaultTextureOptions; defaultTextureOptions.minFilter = GL_LINEAR; defaultTextureOptions.magFilter = GL_LINEAR; @@ -89,6 +94,7 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); - (id)initWithSize:(CGSize)framebufferSize { + _mark = mark; GPUTextureOptions defaultTextureOptions; defaultTextureOptions.minFilter = GL_LINEAR; defaultTextureOptions.magFilter = GL_LINEAR; @@ -136,10 +142,8 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - // By default, all framebuffers on iOS 5.0+ devices are backed by texture caches, using one shared cache if ([GPUImageContext supportsFastTextureUpload]) { -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE CVOpenGLESTextureCacheRef coreVideoTextureCache = [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache]; // Code originally sourced from http://allmybrain.com/2011/12/08/rendering-to-a-texture-with-ios-5-texture-cache-api/ @@ -180,7 +184,6 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, _textureOptions.wrapT); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, CVOpenGLESTextureGetName(renderTexture), 0); -#endif } else { @@ -275,8 +278,10 @@ void dataProviderUnlockCallback (void *info, const void *data, size_t size); if (framebufferReferenceCount < 1) { [[GPUImageContext sharedFramebufferCache] returnFramebufferToCache:self]; - } else if (framebufferReferenceCount == 1) { - fixer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(fixTick) interval:0.3 repeat:false]; + [fixer invalidate]; + fixer = nil; + } else if (framebufferReferenceCount == 1 && self.mark) { + fixer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(fixTick) interval:0.35 repeat:false]; } } @@ -336,7 +341,6 @@ void dataProviderUnlockCallback (void *info, __unused const void *data, __unused CGDataProviderRef dataProvider = NULL; if ([GPUImageContext supportsFastTextureUpload]) { -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE NSUInteger paddedWidthOfImage = (NSUInteger)(CVPixelBufferGetBytesPerRow(renderTarget) / 4.0); NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)_size.height * 4; @@ -346,8 +350,6 @@ void dataProviderUnlockCallback (void *info, __unused const void *data, __unused rawImagePixels = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget); dataProvider = CGDataProviderCreateWithData((__bridge_retained void*)self, rawImagePixels, paddedBytesForImage, dataProviderUnlockCallback); [[GPUImageContext sharedFramebufferCache] addFramebufferToActiveImageCaptureList:self]; // In case the framebuffer is swapped out on the filter, need to have a strong reference to it somewhere for it to hang on while the image is in existence -#else -#endif } else { @@ -362,10 +364,7 @@ void dataProviderUnlockCallback (void *info, __unused const void *data, __unused if ([GPUImageContext supportsFastTextureUpload]) { -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, CVPixelBufferGetBytesPerRow(renderTarget), defaultRGBColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProvider, NULL, NO, kCGRenderingIntentDefault); -#else -#endif } else { diff --git a/submodules/LegacyComponents/Sources/GPUImageFramebufferCache.m b/submodules/LegacyComponents/Sources/GPUImageFramebufferCache.m index 422af4473a..627331b2cb 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFramebufferCache.m +++ b/submodules/LegacyComponents/Sources/GPUImageFramebufferCache.m @@ -94,7 +94,6 @@ { // Nothing in the cache, create a new framebuffer to use framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture]; - framebufferFromCache.mark = mark; } else { @@ -120,7 +119,6 @@ if (framebufferFromCache == nil) { framebufferFromCache = [[GPUImageFramebuffer alloc] initWithSize:framebufferSize textureOptions:textureOptions onlyTexture:onlyTexture]; - framebufferFromCache.mark = mark; } } }); diff --git a/submodules/LegacyComponents/Sources/GPUImageOutput.h b/submodules/LegacyComponents/Sources/GPUImageOutput.h index c8f3178004..2f26d33a50 100755 --- a/submodules/LegacyComponents/Sources/GPUImageOutput.h +++ b/submodules/LegacyComponents/Sources/GPUImageOutput.h @@ -1,21 +1,7 @@ #import "GPUImageContext.h" #import "GPUImageFramebuffer.h" -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE #import -#else -// For now, just redefine this on the Mac -typedef NS_ENUM(NSInteger, UIImageOrientation) { - UIImageOrientationUp, // default orientation - UIImageOrientationDown, // 180 deg rotation - UIImageOrientationLeft, // 90 deg CCW - UIImageOrientationRight, // 90 deg CW - UIImageOrientationUpMirrored, // as above but image mirrored along other axis. horizontal flip - UIImageOrientationDownMirrored, // horizontal flip - UIImageOrientationLeftMirrored, // vertical flip - UIImageOrientationRightMirrored, // vertical flip -}; -#endif void runOnMainQueueWithoutDeadlocking(void (^block)(void)); void runSynchronouslyOnVideoProcessingQueue(void (^block)(void)); @@ -24,17 +10,6 @@ void runSynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void void runAsynchronouslyOnContextQueue(GPUImageContext *context, void (^block)(void)); void reportAvailableMemoryForGPUImage(NSString *tag); -/** GPUImage's base source object - - Images or frames of video are uploaded from source objects, which are subclasses of GPUImageOutput. These include: - - - GPUImageVideoCamera (for live video from an iOS camera) - - GPUImageStillCamera (for taking photos with the camera) - - GPUImagePicture (for still images) - - GPUImageMovie (for movies) - - Source objects upload still image frames to OpenGL ES as textures, then hand those textures off to the next objects in the processing chain. - */ @interface GPUImageOutput : NSObject { GPUImageFramebuffer *outputFramebuffer; @@ -56,7 +31,6 @@ void reportAvailableMemoryForGPUImage(NSString *tag); @property(nonatomic) BOOL enabled; @property(readwrite, nonatomic) GPUTextureOptions outputTextureOptions; -/// @name Managing targets - (void)setInputFramebufferForTarget:(id)target atIndex:(NSInteger)inputTextureIndex; - (GPUImageFramebuffer *)framebufferForOutput; - (void)removeOutputFramebuffer; @@ -106,15 +80,8 @@ void reportAvailableMemoryForGPUImage(NSString *tag); - (void)useNextFrameForImageCapture; - (CGImageRef)newCGImageFromCurrentlyProcessedOutput; -// Platform-specific image output methods -// If you're trying to use these methods, remember that you need to set -useNextFrameForImageCapture before running -processImage or running video and calling any of these methods, or you will get a nil image -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE - (UIImage *)imageFromCurrentFramebuffer; - (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation; -#else -- (NSImage *)imageFromCurrentFramebuffer; -- (NSImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation; -#endif - (BOOL)providesMonochromeOutput; diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureInput.h b/submodules/LegacyComponents/Sources/GPUImageTextureInput.h new file mode 100755 index 0000000000..bd987f5e0c --- /dev/null +++ b/submodules/LegacyComponents/Sources/GPUImageTextureInput.h @@ -0,0 +1,16 @@ +#import "GPUImageOutput.h" +#import + +@interface GPUImageTextureInput : GPUImageOutput +{ + CGSize textureSize; +} + +- (instancetype)initWithTexture:(GLuint)newInputTexture size:(CGSize)newTextureSize; +- (instancetype)initWithCIImage:(CIImage *)ciImage; + +- (void)processTextureWithFrameTime:(CMTime)frameTime synchronous:(bool)synchronous; + +- (CGSize)textureSize; + +@end diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureInput.m b/submodules/LegacyComponents/Sources/GPUImageTextureInput.m new file mode 100755 index 0000000000..12cec3e377 --- /dev/null +++ b/submodules/LegacyComponents/Sources/GPUImageTextureInput.m @@ -0,0 +1,94 @@ +#import "GPUImageTextureInput.h" + +@implementation GPUImageTextureInput + +#pragma mark - +#pragma mark Initialization and teardown + +- (instancetype)initWithTexture:(GLuint)newInputTexture size:(CGSize)newTextureSize +{ + if (!(self = [super init])) + { + return nil; + } + + runSynchronouslyOnVideoProcessingQueue(^{ + [GPUImageContext useImageProcessingContext]; + }); + + textureSize = newTextureSize; + + runSynchronouslyOnVideoProcessingQueue(^{ + outputFramebuffer = [[GPUImageFramebuffer alloc] initWithSize:newTextureSize overriddenTexture:newInputTexture]; + }); + + return self; +} + +- (instancetype)initWithCIImage:(CIImage *)ciImage +{ + EAGLContext *context = [[GPUImageContext sharedImageProcessingContext] context]; + [EAGLContext setCurrentContext:[[GPUImageContext sharedImageProcessingContext] context]]; + + GLsizei backingWidth = ciImage.extent.size.width; + GLsizei backingHeight = ciImage.extent.size.height; + GLuint outputTexture, defaultFramebuffer; + + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &outputTexture); + glBindTexture(GL_TEXTURE_2D, outputTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_2D, 0); + + glActiveTexture(GL_TEXTURE1); + glGenFramebuffers(1, &defaultFramebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); + + glBindTexture(GL_TEXTURE_2D, outputTexture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, backingWidth, backingHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, outputTexture, 0); + + NSAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, @"Incomplete filter FBO: %d", glCheckFramebufferStatus(GL_FRAMEBUFFER)); + + glBindTexture(GL_TEXTURE_2D, 0); + + ciImage = [ciImage imageByApplyingTransform:CGAffineTransformConcat(CGAffineTransformMakeScale(1.0f, -1.0f), CGAffineTransformMakeTranslation(0.0f, ciImage.extent.size.height))]; + + CIContext *ciContext = [CIContext contextWithEAGLContext:context options:@{kCIContextWorkingColorSpace: [NSNull null]}]; + [ciContext drawImage:ciImage inRect:ciImage.extent fromRect:ciImage.extent]; + + if (self = [self initWithTexture:outputTexture size:ciImage.extent.size]) { + textureSize = ciImage.extent.size; + } + return self; +} + +- (void)processTextureWithFrameTime:(CMTime)frameTime synchronous:(bool)synchronous +{ + void (^block)(void) = ^ + { + for (id currentTarget in targets) + { + NSInteger indexOfObject = [targets indexOfObject:currentTarget]; + NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; + + [currentTarget setInputSize:textureSize atIndex:targetTextureIndex]; + [currentTarget setInputFramebuffer:outputFramebuffer atIndex:targetTextureIndex]; + [currentTarget newFrameReadyAtTime:frameTime atIndex:targetTextureIndex]; + } + }; + + if (synchronous) + runSynchronouslyOnVideoProcessingQueue(block); + else + runAsynchronouslyOnVideoProcessingQueue(block); +} + +- (CGSize)textureSize { + return textureSize; +} + +@end diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureOutput.h b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.h new file mode 100755 index 0000000000..51bcd814f4 --- /dev/null +++ b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.h @@ -0,0 +1,24 @@ +#import +#import +#import "GPUImageContext.h" + +@protocol GPUImageTextureOutputDelegate; + +@interface GPUImageTextureOutput : NSObject +{ + GPUImageFramebuffer *firstInputFramebuffer; +} + +@property(readwrite, unsafe_unretained, nonatomic) id delegate; +@property(readonly) GLuint texture; +@property(nonatomic) BOOL enabled; + +- (CIImage *)CIImageWithSize:(CGSize)size; + +- (void)doneWithTexture; + +@end + +@protocol GPUImageTextureOutputDelegate +- (void)newFrameReadyFromTextureOutput:(GPUImageTextureOutput *)callbackTextureOutput; +@end diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m new file mode 100755 index 0000000000..c99012be38 --- /dev/null +++ b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m @@ -0,0 +1,91 @@ +#import "GPUImageTextureOutput.h" + +@implementation GPUImageTextureOutput + +@synthesize delegate = _delegate; +@synthesize texture = _texture; +@synthesize enabled; + +#pragma mark - +#pragma mark Initialization and teardown + +- (id)init; +{ + if (!(self = [super init])) + { + return nil; + } + + self.enabled = YES; + + return self; +} + +- (void)doneWithTexture; +{ + [firstInputFramebuffer unlock]; +} + +#pragma mark - +#pragma mark GPUImageInput protocol + +- (void)newFrameReadyAtTime:(CMTime)frameTime atIndex:(NSInteger)textureIndex; +{ + [_delegate newFrameReadyFromTextureOutput:self]; +} + +- (NSInteger)nextAvailableTextureIndex; +{ + return 0; +} + +- (CIImage *)CIImageWithSize:(CGSize)size +{ + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CIImage *image = [[CIImage alloc] initWithTexture:self.texture size:size flipped:true colorSpace:colorSpace]; + CGColorSpaceRelease(colorSpace); + return image; +} + +// TODO: Deal with the fact that the texture changes regularly as a result of the caching +- (void)setInputFramebuffer:(GPUImageFramebuffer *)newInputFramebuffer atIndex:(NSInteger)textureIndex; +{ + firstInputFramebuffer = newInputFramebuffer; + [firstInputFramebuffer lock]; + + _texture = [firstInputFramebuffer texture]; +} + +- (void)setInputRotation:(GPUImageRotationMode)newInputRotation atIndex:(NSInteger)textureIndex; +{ +} + +- (void)setInputSize:(CGSize)newSize atIndex:(NSInteger)textureIndex; +{ +} + +- (CGSize)maximumOutputSize; +{ + return CGSizeZero; +} + +- (void)endProcessing +{ +} + +- (BOOL)shouldIgnoreUpdatesToThisTarget; +{ + return NO; +} + +- (BOOL)wantsMonochromeInput; +{ + return NO; +} + +- (void)setCurrentlyReceivingMonochromeInput:(BOOL)newValue; +{ + +} + +@end diff --git a/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m b/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m index cbed53166d..494622075b 100644 --- a/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m +++ b/submodules/LegacyComponents/Sources/LegacyComponentsInternal.m @@ -152,5 +152,9 @@ UIImage *TGComponentsImageNamed(NSString *name) { } NSString *TGComponentsPathForResource(NSString *name, NSString *type) { - return [resourcesBundle() pathForResource:name ofType:type]; + NSBundle *bundle = resourcesBundle(); + if (bundle == nil) { + bundle = getAppBundle(); + } + return [bundle pathForResource:name ofType:type]; } diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.h b/submodules/LegacyComponents/Sources/PGPhotoEditor.h index a0205ea305..d0a5f31af3 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.h +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.h @@ -37,11 +37,13 @@ - (void)setImage:(UIImage *)image forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored fullSize:(bool)fullSize; - (void)setVideoAsset:(AVAsset *)asset; +- (void)setCIImage:(CIImage *)ciImage; - (void)processAnimated:(bool)animated completion:(void (^)(void))completion; - (void)createResultImageWithCompletion:(void (^)(UIImage *image))completion; - (UIImage *)currentResultImage; +- (CIImage *)currentResultCIImage; - (bool)hasDefaultCropping; diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.m b/submodules/LegacyComponents/Sources/PGPhotoEditor.m index 3bad0f1aa4..cb7eebfc56 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.m +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.m @@ -10,6 +10,9 @@ #import "PGPhotoEditorView.h" #import "PGPhotoEditorPicture.h" +#import "GPUImageTextureInput.h" +#import "GPUImageTextureOutput.h" + #import #import #import @@ -44,6 +47,8 @@ NSArray *_currentProcessChain; GPUImageOutput *_finalFilter; + GPUImageTextureOutput *_textureOutput; + PGPhotoHistogram *_currentHistogram; PGPhotoHistogramGenerator *_histogramGenerator; @@ -167,6 +172,21 @@ _fullSize = true; } +- (void)setCIImage:(CIImage *)ciImage { + [_toolComposer invalidate]; + _currentProcessChain = nil; + + [_currentInput removeAllTargets]; + GPUImageTextureInput *input = [[GPUImageTextureInput alloc] initWithCIImage:ciImage]; + _currentInput = input; + + if (_textureOutput == nil) { + _textureOutput = [[GPUImageTextureOutput alloc] init]; + } + + _fullSize = true; +} + #pragma mark - Properties - (CGSize)rotatedCropSize @@ -201,7 +221,7 @@ - (void)processAnimated:(bool)animated capture:(bool)capture synchronous:(bool)synchronous completion:(void (^)(void))completion { - if (self.previewOutput == nil) + if (self.previewOutput == nil && ![_currentInput isKindOfClass:[GPUImageTextureInput class]]) return; if (self.forVideo) { @@ -210,16 +230,21 @@ [self updateProcessChain]; GPUImageOutput *currentInput = _currentInput; - - if (!_playing) { - _playing = true; - [_videoQueue dispatch:^{ - if ([currentInput isKindOfClass:[PGVideoMovie class]]) { - [(PGVideoMovie *)currentInput startProcessing]; - } - }]; + if ([currentInput isKindOfClass:[PGVideoMovie class]]) { + if (!_playing) { + _playing = true; + [_videoQueue dispatch:^{ + if ([currentInput isKindOfClass:[PGVideoMovie class]]) { + [(PGVideoMovie *)currentInput startProcessing]; + } + }]; + } + } else if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) { + [(GPUImageTextureInput *)currentInput processTextureWithFrameTime:kCMTimeZero synchronous:synchronous]; + if (completion != nil) + completion(); } - }]; + } synchronous:synchronous]; return; } @@ -281,6 +306,8 @@ } - (void)updateProcessChain { + [GPUImageFramebuffer setMark:self.forVideo]; + NSMutableArray *processChain = [NSMutableArray array]; for (PGPhotoTool *tool in _toolComposer.advancedTools) @@ -319,10 +346,17 @@ } _finalFilter = lastFilter; - [_finalFilter addTarget:previewOutput.imageView]; + if (_textureOutput != nil) { + [_finalFilter addTarget:_textureOutput]; + } + + if (previewOutput != nil) { + [_finalFilter addTarget:previewOutput.imageView]; + } - if (!self.forVideo) + if (!self.forVideo) { [_finalFilter addTarget:_histogramGenerator]; + } } } @@ -349,6 +383,18 @@ return image; } +- (CIImage *)currentResultCIImage { + __block CIImage *image = nil; + GPUImageOutput *currentInput = _currentInput; + [self processAnimated:false capture:false synchronous:true completion:^ + { + if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) { + image = [_textureOutput CIImageWithSize:[(GPUImageTextureInput *)currentInput textureSize]]; + } + }]; + return image; +} + #pragma mark - Editor Values - (void)_importAdjustments:(id)adjustments diff --git a/submodules/LegacyComponents/Sources/PGVideoMovie.m b/submodules/LegacyComponents/Sources/PGVideoMovie.m index 6fed7835a9..49ecafb5c2 100755 --- a/submodules/LegacyComponents/Sources/PGVideoMovie.m +++ b/submodules/LegacyComponents/Sources/PGVideoMovie.m @@ -14,9 +14,9 @@ GLfloat kColorConversion601FullRangeDefault[] = { }; GLfloat kColorConversion709Default[] = { - 1.164, 1.164, 1.164, - 0.0, -0.213, 2.112, - 1.793, -0.533, 0.0, + 1, 1, 1, + 0, -.21482, 2.12798, + 1.28033, -.38059, 0, }; GLfloat *kColorConversion601 = kColorConversion601Default; @@ -124,7 +124,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN #pragma mark Initialization and teardown -- (instancetype)initWithAsset:(AVAsset *)asset; +- (instancetype)initWithAsset:(AVAsset *)asset { if (!(self = [super init])) { @@ -138,7 +138,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN return self; } -- (void)yuvConversionSetup; +- (void)yuvConversionSetup { if ([GPUImageContext supportsFastTextureUpload]) { @@ -147,7 +147,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN _preferredConversion = kColorConversion709; isFullYUVRange = YES; - yuvConversionProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kYUVFullRangeConversionForLAFragmentShaderString]; + yuvConversionProgram = [[GPUImageContext sharedImageProcessingContext] programForVertexShaderString:kGPUImageVertexShaderString fragmentShaderString:kYUVVideoRangeConversionForRGFragmentShaderString]; if (!yuvConversionProgram.initialized) { @@ -213,7 +213,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN { NSError *error = nil; AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:self.asset error:&error]; - + NSMutableDictionary *outputSettings = [NSMutableDictionary dictionary]; if ([GPUImageContext supportsFastTextureUpload]) { [outputSettings setObject:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) forKey:(id)kCVPixelBufferPixelFormatTypeKey]; @@ -224,7 +224,6 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN isFullYUVRange = NO; } - // Maybe set alwaysCopiesSampleData to NO on iOS 5.0 for faster video decoding AVAssetReaderTrackOutput *readerVideoTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[[self.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] outputSettings:outputSettings]; readerVideoTrackOutput.alwaysCopiesSampleData = NO; [assetReader addOutput:readerVideoTrackOutput]; @@ -485,8 +484,6 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN } } - - CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent(); [GPUImageContext useImageProcessingContext]; @@ -510,7 +507,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN glActiveTexture(GL_TEXTURE4); if ([GPUImageContext deviceSupportsRedTextures]) { - err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], movieFrame, NULL, GL_TEXTURE_2D, GL_LUMINANCE, bufferWidth, bufferHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, &luminanceTextureRef); + err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], movieFrame, NULL, GL_TEXTURE_2D, GL_RED_EXT, bufferWidth, bufferHeight, GL_RED_EXT, GL_UNSIGNED_BYTE, 0, &luminanceTextureRef); } else { @@ -531,7 +528,7 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN glActiveTexture(GL_TEXTURE5); if ([GPUImageContext deviceSupportsRedTextures]) { - err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], movieFrame, NULL, GL_TEXTURE_2D, GL_LUMINANCE_ALPHA, bufferWidth/2, bufferHeight/2, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 1, &chrominanceTextureRef); + err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], movieFrame, NULL, GL_TEXTURE_2D, GL_RG_EXT, bufferWidth/2, bufferHeight/2, GL_RG_EXT, GL_UNSIGNED_BYTE, 1, &chrominanceTextureRef); } else { @@ -556,6 +553,8 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN NSInteger targetTextureIndex = [[targetTextureIndices objectAtIndex:indexOfObject] integerValue]; [currentTarget setInputSize:CGSizeMake(bufferWidth, bufferHeight) atIndex:targetTextureIndex]; [currentTarget setInputFramebuffer:outputFramebuffer atIndex:targetTextureIndex]; + +// [currentTarget setInputRotation:kGPUImageRotateLeft atIndex:targetTextureIndex]; } [outputFramebuffer unlock]; diff --git a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m index 7b36327c5b..3298c0bd39 100644 --- a/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m +++ b/submodules/LegacyComponents/Sources/TGAttachmentCarouselItemView.m @@ -801,7 +801,7 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500; if ([cell isKindOfClass:[TGAttachmentAssetCell class]]) thumbnailImage = cell.imageView.image; - TGMediaPickerModernGalleryMixin *mixin = [[TGMediaPickerModernGalleryMixin alloc] initWithContext:_context item:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaptions:(_allowCaptions && !_forProfilePhoto) allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:_inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:self.asFile itemsLimit:TGAttachmentDisplayedAssetLimit recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder]; + TGMediaPickerModernGalleryMixin *mixin = [[TGMediaPickerModernGalleryMixin alloc] initWithContext:_context item:asset fetchResult:_fetchResult parentController:self.parentController thumbnailImage:thumbnailImage selectionContext:_selectionContext editingContext:_editingContext suggestionContext:self.suggestionContext hasCaptions:(_allowCaptions && !_forProfilePhoto) allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:_inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:self.asFile itemsLimit:TGAttachmentDisplayedAssetLimit recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder stickersContext:self.stickersContext]; mixin.presentScheduleController = self.presentScheduleController; __weak TGAttachmentCarouselItemView *weakSelf = self; mixin.thumbnailSignalForItem = ^SSignal *(id item) diff --git a/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m b/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m index aa650c39ee..1f88d4701b 100644 --- a/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m +++ b/submodules/LegacyComponents/Sources/TGCameraPhotoPreviewController.m @@ -135,7 +135,7 @@ CGRect containerFrame = self.view.bounds; CGSize fittedSize = TGScaleToSize(_image.size, containerFrame.size); - _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds]; + _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds hasDoubleTap:true]; _scrollView.clipsToBounds = false; _scrollView.delegate = self; _scrollView.showsHorizontalScrollIndicator = false; diff --git a/submodules/LegacyComponents/Sources/TGClipboardMenu.m b/submodules/LegacyComponents/Sources/TGClipboardMenu.m index 27484b495b..c646e301c6 100644 --- a/submodules/LegacyComponents/Sources/TGClipboardMenu.m +++ b/submodules/LegacyComponents/Sources/TGClipboardMenu.m @@ -42,6 +42,7 @@ TGClipboardPreviewItemView *previewItem = [[TGClipboardPreviewItemView alloc] initWithContext:context images:images]; __weak TGClipboardPreviewItemView *weakPreviewItem = previewItem; + previewItem.suggestionContext = suggestionContext; previewItem.parentController = parentController; previewItem.allowCaptions = hasCaption; previewItem.hasTimer = hasTimer; diff --git a/submodules/LegacyComponents/Sources/TGFont.mm b/submodules/LegacyComponents/Sources/TGFont.mm index 446a717b0b..9c66d82e67 100644 --- a/submodules/LegacyComponents/Sources/TGFont.mm +++ b/submodules/LegacyComponents/Sources/TGFont.mm @@ -117,8 +117,8 @@ UIFont *TGFixedSystemFontOfSize(CGFloat size) + (UIFont *)roundedFontOfSize:(CGFloat)size { if (@available(iOSApplicationExtension 13.0, iOS 13.0, *)) { - UIFontDescriptor *descriptor = [UIFont systemFontOfSize: size].fontDescriptor; - descriptor = [descriptor fontDescriptorWithDesign: UIFontDescriptorSystemDesignRounded]; + UIFontDescriptor *descriptor = [UIFont boldSystemFontOfSize: size].fontDescriptor; + descriptor = [descriptor fontDescriptorWithDesign:UIFontDescriptorSystemDesignRounded]; return [UIFont fontWithDescriptor:descriptor size:size]; } else { return [UIFont fontWithName:@".SFCompactRounded-Semibold" size:size]; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 8c6fd58736..44aee76b8b 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -110,6 +110,7 @@ pickerController.pallete = strongController.pallete; } pickerController.suggestionContext = strongController.suggestionContext; + pickerController.stickersContext = strongController.stickersContext; pickerController.localMediaCacheEnabled = strongController.localMediaCacheEnabled; pickerController.captionsEnabled = strongController.captionsEnabled; pickerController.allowCaptionEntities = strongController.allowCaptionEntities; @@ -149,6 +150,12 @@ self.pickerController.suggestionContext = suggestionContext; } +- (void)setStickersContext:(id)stickersContext +{ + _stickersContext = stickersContext; + self.pickerController.stickersContext = stickersContext; +} + - (void)setCaptionsEnabled:(bool)captionsEnabled { _captionsEnabled = captionsEnabled; @@ -875,7 +882,11 @@ if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting) { CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height); - return TGPhotoEditorCrop(image, adjustments.paintingData.image, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize); + UIImage *paintingImage = adjustments.paintingData.stillImage; + if (paintingImage == nil) { + paintingImage = adjustments.paintingData.image; + } + return TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize); } return image; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m index ad92688326..346f2481df 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m @@ -323,7 +323,7 @@ - (TGMediaPickerModernGalleryMixin *)_galleryMixinForContext:(id)context item:(id)item thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities inhibitDocumentCaptions:(bool)inhibitDocumentCaptions asFile:(bool)asFile { - return [[TGMediaPickerModernGalleryMixin alloc] initWithContext:context item:item fetchResult:_fetchResult parentController:self thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:asFile itemsLimit:0 recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder]; + return [[TGMediaPickerModernGalleryMixin alloc] initWithContext:context item:item fetchResult:_fetchResult parentController:self thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:self.hasTimer onlyCrop:self.onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:self.inhibitMute asFile:asFile itemsLimit:0 recipientName:self.recipientName hasSilentPosting:self.hasSilentPosting hasSchedule:self.hasSchedule reminder:self.reminder stickersContext:self.stickersContext]; } - (TGMediaPickerModernGalleryMixin *)galleryMixinForIndexPath:(NSIndexPath *)indexPath previewMode:(bool)previewMode outAsset:(TGMediaAsset **)outAsset diff --git a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m index f9ddc6324d..fc5f82f9e0 100644 --- a/submodules/LegacyComponents/Sources/TGMediaEditingContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaEditingContext.m @@ -78,6 +78,7 @@ TGMemoryImageCache *_thumbnailImageCache; TGMemoryImageCache *_paintingImageCache; + TGMemoryImageCache *_stillPaintingImageCache; TGMemoryImageCache *_originalImageCache; TGMemoryImageCache *_originalThumbnailImageCache; @@ -86,6 +87,7 @@ NSURL *_fullSizeResultsUrl; NSURL *_paintingDatasUrl; NSURL *_paintingImagesUrl; + NSURL *_stillPaintingImagesUrl; NSURL *_videoPaintingImagesUrl; NSMutableArray *_storeVideoPaintingImages; @@ -131,6 +133,9 @@ _paintingImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] imageSoftMemoryLimit] hardMemoryLimit:[[self class] imageHardMemoryLimit]]; + _stillPaintingImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] imageSoftMemoryLimit] + hardMemoryLimit:[[self class] imageHardMemoryLimit]]; + _originalImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] originalImageSoftMemoryLimit] hardMemoryLimit:[[self class] originalImageHardMemoryLimit]]; _originalThumbnailImageCache = [[TGMemoryImageCache alloc] initWithSoftMemoryLimit:[[self class] thumbnailImageSoftMemoryLimit] @@ -145,6 +150,9 @@ _paintingImagesUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:[NSString stringWithFormat:@"paintingimages/%@", _contextId]]]; [[NSFileManager defaultManager] createDirectoryAtPath:_paintingImagesUrl.path withIntermediateDirectories:true attributes:nil error:nil]; + _stillPaintingImagesUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"stillpaintingimages"]]; + [[NSFileManager defaultManager] createDirectoryAtPath:_stillPaintingImagesUrl.path withIntermediateDirectories:true attributes:nil error:nil]; + _videoPaintingImagesUrl = [NSURL fileURLWithPath:[[[LegacyComponentsGlobals provider] dataStoragePath] stringByAppendingPathComponent:@"videopaintingimages"]]; [[NSFileManager defaultManager] createDirectoryAtPath:_videoPaintingImagesUrl.path withIntermediateDirectories:true attributes:nil error:nil]; @@ -340,6 +348,27 @@ return result; } +- (UIImage *)stillPaintingImageForItem:(NSObject *)item +{ + NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; + if (itemId == nil) + return nil; + + UIImage *result = [_stillPaintingImageCache imageForKey:itemId attributes:NULL]; + if (result == nil) + { + NSURL *imageUrl = [_stillPaintingImagesUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", [TGStringUtils md5:itemId]]]; + UIImage *diskImage = [UIImage imageWithContentsOfFile:imageUrl.path]; + if (diskImage != nil) + { + result = diskImage; + [_stillPaintingImageCache setImage:result forKey:itemId attributes:NULL]; + } + } + + return result; +} + #pragma mark - Caption - (NSString *)captionForItem:(id)item @@ -602,7 +631,7 @@ [_queue dispatch:block]; } -- (bool)setPaintingData:(NSData *)data image:(UIImage *)image forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video +- (bool)setPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage forItem:(NSObject *)item dataUrl:(NSURL **)dataOutUrl imageUrl:(NSURL **)imageOutUrl forVideo:(bool)video { NSString *itemId = [self _contextualIdForItemId:item.uniqueIdentifier]; @@ -624,10 +653,21 @@ if (dataSuccess && dataOutUrl != NULL) *dataOutUrl = dataUrl; - + if (video) [_storeVideoPaintingImages addObject:imageUrl]; + if (stillImage != nil) { + NSURL *stillImageUrl = [_stillPaintingImagesUrl URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.png", [TGStringUtils md5:itemId]]]; + [_stillPaintingImageCache setImage:stillImage forKey:itemId attributes:NULL]; + + NSData *stillImageData = UIImagePNGRepresentation(stillImage); + [stillImageData writeToURL:stillImageUrl options:NSDataWritingAtomic error:nil]; + + if (video) + [_storeVideoPaintingImages addObject:stillImageUrl]; + } + return (image == nil || imageSuccess) && (data == nil || dataSuccess); } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index 7bcdea25b5..d08466c329 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -164,7 +164,7 @@ if (recipientName.length > 0) { - _arrowView = [[UIImageView alloc] initWithImage:TGComponentsImageNamed(@"PhotoPickerArrow")]; + _arrowView = [[UIImageView alloc] initWithImage: TGTintedImage([UIImage imageNamed:@"Editor/Recipient"], UIColor.whiteColor)]; _arrowView.alpha = 0.45f; [_wrapperView addSubview:_arrowView]; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m index e6ca8b1ca5..9942634dac 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m @@ -387,6 +387,7 @@ TGPhotoEditorControllerIntent intent = isVideo ? TGPhotoEditorControllerVideoIntent : TGPhotoEditorControllerGenericIntent; TGPhotoEditorController *controller = [[TGPhotoEditorController alloc] initWithContext:_context item:item.editableMediaItem intent:intent adjustments:editorValues caption:caption screenImage:screenImage availableTabs:_interfaceView.currentTabs selectedTab:tab]; controller.editingContext = _editingContext; + controller.stickersContext = _stickersContext; self.editorController = controller; controller.suggestionContext = self.suggestionContext; controller.willFinishEditing = ^(id adjustments, id temporaryRep, bool hasChanges) diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index 2c9535ec3d..e8d0082b1d 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -166,7 +166,8 @@ _actionButton.highlightImage = highlightImage; - _progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)]; + _progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)]; + [_progressView setRadius:60.0]; _progressView.userInteractionEnabled = false; [_progressView setPlay]; [_actionButton addSubview:_progressView]; @@ -864,7 +865,8 @@ if (progressVisible && _progressView == nil) { - _progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 50.0f, 50.0f)]; + _progressView = [[TGMessageImageViewOverlayView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 60.0f, 60.0f)]; + [_progressView setRadius:60.0]; _progressView.userInteractionEnabled = false; _progressView.frame = (CGRect){{CGFloor((self.frame.size.width - _progressView.frame.size.width) / 2.0f), CGFloor((self.frame.size.height - _progressView.frame.size.height) / 2.0f)}, _progressView.frame.size}; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m b/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m index a2e2b5292b..b62ee3b8b3 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerModernGalleryMixin.m @@ -41,17 +41,17 @@ @implementation TGMediaPickerModernGalleryMixin -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext { - return [self initWithContext:context item:item fetchResult:fetchResult momentList:nil parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:recipientName hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder]; + return [self initWithContext:context item:item fetchResult:fetchResult momentList:nil parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:recipientName hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder stickersContext:stickersContext]; } -- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder +- (instancetype)initWithContext:(id)context item:(id)item momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext { - return [self initWithContext:context item:item fetchResult:nil momentList:momentList parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:nil hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder]; + return [self initWithContext:context item:item fetchResult:nil momentList:momentList parentController:parentController thumbnailImage:thumbnailImage selectionContext:selectionContext editingContext:editingContext suggestionContext:suggestionContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions inhibitMute:inhibitMute asFile:asFile itemsLimit:itemsLimit recipientName:nil hasSilentPosting:hasSilentPosting hasSchedule:hasSchedule reminder:reminder stickersContext:stickersContext]; } -- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder +- (instancetype)initWithContext:(id)context item:(id)item fetchResult:(TGMediaAssetFetchResult *)fetchResult momentList:(TGMediaAssetMomentList *)momentList parentController:(TGViewController *)parentController thumbnailImage:(UIImage *)thumbnailImage selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext suggestionContext:(TGSuggestionContext *)suggestionContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions inhibitMute:(bool)inhibitMute asFile:(bool)asFile itemsLimit:(NSUInteger)itemsLimit recipientName:(NSString *)recipientName hasSilentPosting:(bool)hasSilentPosting hasSchedule:(bool)hasSchedule reminder:(bool)reminder stickersContext:(id)stickersContext { self = [super init]; if (self != nil) @@ -85,6 +85,7 @@ TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:[_windowManager context] items:galleryItems focusItem:focusItem selectionContext:selectionContext editingContext:editingContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions hasSelectionPanel:true hasCamera:false recipientName:recipientName]; _galleryModel = model; + model.stickersContext = stickersContext; model.inhibitMute = inhibitMute; model.controller = modernGallery; model.suggestionContext = suggestionContext; diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 6e2f86967d..4501fea098 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -3,13 +3,18 @@ #import #import +#import "GPUImageContext.h" + #import "LegacyComponentsInternal.h" #import "TGImageUtils.h" #import "TGPhotoEditorUtils.h" +#import "PGPhotoEditor.h" +#import "TGPhotoPaintEntity.h" #import "TGVideoEditAdjustments.h" #import "TGPaintingData.h" +#import "TGPhotoPaintStickersContext.h" @interface TGMediaVideoConversionPresetSettings () @@ -68,6 +73,8 @@ @property (nonatomic, readonly) TGMediaSampleBufferProcessor *videoProcessor; @property (nonatomic, readonly) TGMediaSampleBufferProcessor *audioProcessor; +@property (nonatomic, readonly) id entityRenderer; + @property (nonatomic, readonly) CMTimeRange timeRange; @property (nonatomic, readonly) CGSize dimensions; @property (nonatomic, readonly) UIImage *coverImage; @@ -79,7 +86,7 @@ - (instancetype)addImageGenerator:(AVAssetImageGenerator *)imageGenerator; - (instancetype)addCoverImage:(UIImage *)coverImage; -- (instancetype)contextWithAssetReader:(AVAssetReader *)assetReader assetWriter:(AVAssetWriter *)assetWriter videoProcessor:(TGMediaSampleBufferProcessor *)videoProcessor audioProcessor:(TGMediaSampleBufferProcessor *)audioProcessor timeRange:(CMTimeRange)timeRange dimensions:(CGSize)dimensions; +- (instancetype)contextWithAssetReader:(AVAssetReader *)assetReader assetWriter:(AVAssetWriter *)assetWriter videoProcessor:(TGMediaSampleBufferProcessor *)videoProcessor audioProcessor:(TGMediaSampleBufferProcessor *)audioProcessor timeRange:(CMTimeRange)timeRange dimensions:(CGSize)dimensions entityRenderer:(id)entityRenderer; @end @@ -93,12 +100,12 @@ @implementation TGMediaVideoConverter -+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher ++ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id)entityRenderer { - return [self convertAVAsset:avAsset adjustments:adjustments watcher:watcher inhibitAudio:false]; + return [self convertAVAsset:avAsset adjustments:adjustments watcher:watcher inhibitAudio:false entityRenderer:entityRenderer]; } -+ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio ++ (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id)entityRenderer { SQueue *queue = [[SQueue alloc] init]; @@ -146,7 +153,7 @@ } } - if (![self setupAssetReaderWriterForAVAsset:avAsset outputURL:outputUrl preset:preset adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error]) + if (![self setupAssetReaderWriterForItem:avAsset outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error]) { [subscriber putError:error]; return; @@ -204,6 +211,92 @@ }]; } ++ (SSignal *)renderUIImage:(UIImage *)image adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id)entityRenderer +{ + SQueue *queue = [[SQueue alloc] init]; + + return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber) + { + SAtomic *context = [[SAtomic alloc] initWithValue:[TGMediaVideoConversionContext contextWithQueue:queue subscriber:subscriber]]; + NSURL *outputUrl = [self _randomTemporaryURL]; + + [queue dispatch:^ + { + if (((TGMediaVideoConversionContext *)context.value).cancelled) + return; + + TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetAnimation; + + NSError *error = nil; + + NSString *outputPath = outputUrl.path; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:outputPath]) + { + [fileManager removeItemAtPath:outputPath error:&error]; + if (error != nil) + { + [subscriber putError:error]; + return; + } + } + + if (![self setupAssetReaderWriterForItem:image outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:true conversionContext:context error:&error]) + { + [subscriber putError:error]; + return; + } + + TGDispatchAfter(1.0, queue._dispatch_queue, ^ + { + if (watcher != nil) + [watcher setupWithFileURL:outputUrl]; + }); + + [self processWithConversionContext:context completionBlock:^ + { + TGMediaVideoConversionContext *resultContext = context.value; + [resultContext.imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(__unused CMTime requestedTime, CGImageRef _Nullable image, __unused CMTime actualTime, AVAssetImageGeneratorResult result, __unused NSError * _Nullable error) + { + UIImage *coverImage = nil; + if (result == AVAssetImageGeneratorSucceeded) + coverImage = [UIImage imageWithCGImage:image]; + + __block TGMediaVideoConversionResult *contextResult = nil; + [context modify:^id(TGMediaVideoConversionContext *resultContext) + { + id liveUploadData = nil; + if (watcher != nil) + liveUploadData = [watcher fileUpdated:true]; + + contextResult = [TGMediaVideoConversionResult resultWithFileURL:outputUrl fileSize:0 duration:CMTimeGetSeconds(resultContext.timeRange.duration) dimensions:resultContext.dimensions coverImage:coverImage liveUploadData:liveUploadData]; + return [resultContext finishedContext]; + }]; + + [subscriber putNext:contextResult]; + [subscriber putCompletion]; + }]; + }]; + }]; + + return [[SBlockDisposable alloc] initWithBlock:^ + { + [queue dispatch:^ + { + [context modify:^id(TGMediaVideoConversionContext *currentContext) + { + if (currentContext.finished) + return currentContext; + + [currentContext.videoProcessor cancel]; + + return [currentContext cancelledContext]; + }]; + }]; + }]; + }]; +} + + (CGSize)dimensionsFor:(CGSize)dimensions adjustments:(TGMediaVideoEditAdjustments *)adjustments preset:(TGMediaVideoConversionPreset)preset { CGRect transformedRect = CGRectMake(0.0f, 0.0f, dimensions.width, dimensions.height); @@ -221,7 +314,7 @@ return outputDimensions; } -+ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext ++ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext { CGSize transformedSize = CGRectApplyAffineTransform((CGRect){CGPointZero, videoTrack.naturalSize}, videoTrack.preferredTransform).size;; CGRect transformedRect = CGRectMake(0, 0, transformedSize.width, transformedSize.height); @@ -240,8 +333,81 @@ if (TGOrientationIsSideward(adjustments.cropOrientation, NULL)) outputDimensions = CGSizeMake(outputDimensions.height, outputDimensions.width); + + AVMutableCompositionTrack *trimVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + [trimVideoTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:NULL]; - AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition]; + UIImage *overlayImage = nil; + if (adjustments.paintingData.imagePath != nil) + overlayImage = [UIImage imageWithContentsOfFile:adjustments.paintingData.imagePath]; + + bool hasAnimation = false; + for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { + if (entity.animated) { + hasAnimation = true; + break; + } + } + if (!hasAnimation) { + entityRenderer = nil; + } + + AVMutableVideoComposition *videoComposition; + if (entityRenderer != nil || adjustments.toolsApplied) { + PGPhotoEditor *editor = nil; + CIContext *ciContext = nil; + if (adjustments.toolsApplied) { + editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:true enableStickers:true]; + ciContext = [CIContext contextWithEAGLContext:[[GPUImageContext sharedImageProcessingContext] context]]; + } + + __block CIImage *overlayCIImage = nil; + videoComposition = [AVMutableVideoComposition videoCompositionWithAsset:avAsset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) { + __block CIImage *resultImage = request.sourceImage; + + if (editor != nil) { + [editor setCIImage:resultImage]; + resultImage = editor.currentResultCIImage; + } + + if (overlayImage != nil && overlayImage.size.width > 0.0) { + if (overlayCIImage == nil) { + overlayCIImage = [[CIImage alloc] initWithImage:overlayImage]; + CGFloat scale = request.sourceImage.extent.size.width / overlayCIImage.extent.size.width; + overlayCIImage = [overlayCIImage imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)]; + } + resultImage = [overlayCIImage imageByCompositingOverImage:resultImage]; + } + + if (entityRenderer != nil) { + [entityRenderer entitiesForTime:request.compositionTime size:request.sourceImage.extent.size completion:^(NSArray *images) { + for (CIImage *image in images) { + resultImage = [image imageByCompositingOverImage:resultImage]; + } + [request finishWithImage:resultImage context:ciContext]; + }]; + } else { + [request finishWithImage:resultImage context:ciContext]; + } + }]; + } else { + videoComposition = [AVMutableVideoComposition videoComposition]; + + bool mirrored = false; + UIImageOrientation videoOrientation = TGVideoOrientationForAsset(avAsset, &mirrored); + CGAffineTransform transform = TGVideoTransformForOrientation(videoOrientation, videoTrack.naturalSize, cropRect, mirrored); + CGAffineTransform rotationTransform = TGVideoTransformForCrop(adjustments.cropOrientation, cropRect.size, adjustments.cropMirrored); + CGAffineTransform finalTransform = CGAffineTransformConcat(transform, rotationTransform); + + AVMutableVideoCompositionLayerInstruction *transformer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:trimVideoTrack]; + [transformer setTransform:finalTransform atTime:kCMTimeZero]; + + AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; + instruction.timeRange = CMTimeRangeMake(kCMTimeZero, timeRange.duration); + instruction.layerInstructions = [NSArray arrayWithObject:transformer]; + videoComposition.instructions = [NSArray arrayWithObject:instruction]; + } + if (videoTrack.nominalFrameRate > 0) videoComposition.frameDuration = CMTimeMake(1, (int32_t)videoTrack.nominalFrameRate); else if (CMTimeCompare(videoTrack.minFrameDuration, kCMTimeZero) == 1) @@ -258,60 +424,39 @@ videoComposition.renderSize = [self _renderSizeWithCropSize:cropRect.size rotateSideward:TGOrientationIsSideward(adjustments.cropOrientation, NULL)]; if (videoComposition.renderSize.width < FLT_EPSILON || videoComposition.renderSize.height < FLT_EPSILON) return nil; - - AVMutableCompositionTrack *trimVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; - [trimVideoTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:NULL]; - - bool mirrored = false; - UIImageOrientation videoOrientation = TGVideoOrientationForAsset(avAsset, &mirrored); - CGAffineTransform transform = TGVideoTransformForOrientation(videoOrientation, videoTrack.naturalSize, cropRect, mirrored); - CGAffineTransform rotationTransform = TGVideoTransformForCrop(adjustments.cropOrientation, cropRect.size, adjustments.cropMirrored); - CGAffineTransform finalTransform = CGAffineTransformConcat(transform, rotationTransform); - - AVMutableVideoCompositionLayerInstruction *transformer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:trimVideoTrack]; - [transformer setTransform:finalTransform atTime:kCMTimeZero]; - - AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; - instruction.timeRange = CMTimeRangeMake(kCMTimeZero, timeRange.duration); - instruction.layerInstructions = [NSArray arrayWithObject:transformer]; - videoComposition.instructions = [NSArray arrayWithObject:instruction]; - - UIImage *overlayImage = nil; - if (adjustments.paintingData.imagePath != nil) - overlayImage = [UIImage imageWithContentsOfFile:adjustments.paintingData.imagePath]; - - if (overlayImage != nil) + + if (overlayImage != nil && entityRenderer == nil) { CALayer *parentLayer = [CALayer layer]; parentLayer.frame = CGRectMake(0, 0, videoComposition.renderSize.width, videoComposition.renderSize.height); - + CALayer *videoLayer = [CALayer layer]; videoLayer.frame = parentLayer.frame; [parentLayer addSublayer:videoLayer]; - + CGSize parentSize = parentLayer.bounds.size; if (TGOrientationIsSideward(adjustments.cropOrientation, NULL)) parentSize = CGSizeMake(parentSize.height, parentSize.width); - + CGSize size = CGSizeMake(parentSize.width * transformedSize.width / cropRect.size.width, parentSize.height * transformedSize.height / cropRect.size.height); CGPoint origin = CGPointMake(-parentSize.width / cropRect.size.width * cropRect.origin.x, -parentSize.height / cropRect.size.height * (transformedSize.height - cropRect.size.height - cropRect.origin.y)); - + CALayer *rotationLayer = [CALayer layer]; rotationLayer.frame = CGRectMake(0, 0, parentSize.width, parentSize.height); [parentLayer addSublayer:rotationLayer]; - + UIImageOrientation orientation = TGMirrorSidewardOrientation(adjustments.cropOrientation); CATransform3D layerTransform = CATransform3DMakeTranslation(rotationLayer.frame.size.width / 2.0f, rotationLayer.frame.size.height / 2.0f, 0.0f); layerTransform = CATransform3DRotate(layerTransform, TGRotationForOrientation(orientation), 0.0f, 0.0f, 1.0f); layerTransform = CATransform3DTranslate(layerTransform, -parentLayer.bounds.size.width / 2.0f, -parentLayer.bounds.size.height / 2.0f, 0.0f); rotationLayer.transform = layerTransform; rotationLayer.frame = parentLayer.frame; - + CALayer *overlayLayer = [CALayer layer]; overlayLayer.contents = (id)overlayImage.CGImage; overlayLayer.frame = CGRectMake(origin.x, origin.y, size.width, size.height); [rotationLayer addSublayer:overlayLayer]; - + videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer]; } @@ -335,83 +480,113 @@ return output; } -+ (bool)setupAssetReaderWriterForAVAsset:(AVAsset *)avAsset outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error ++ (bool)setupAssetReaderWriterForItem:(id)item outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error { - TGMediaSampleBufferProcessor *videoProcessor = nil; - TGMediaSampleBufferProcessor *audioProcessor = nil; - - AVAssetTrack *audioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] firstObject]; - AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; - if (videoTrack == nil) - return false; - - CGSize dimensions = CGSizeZero; - CMTimeRange timeRange = videoTrack.timeRange; - if (adjustments.trimApplied) - { - NSTimeInterval duration = CMTimeGetSeconds(videoTrack.timeRange.duration); - if (adjustments.trimEndValue < duration) - { - timeRange = adjustments.trimTimeRange; - } - else - { - timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC), CMTimeMakeWithSeconds(duration - adjustments.trimStartValue, NSEC_PER_SEC)); - } - } - timeRange = CMTimeRangeMake(CMTimeAdd(timeRange.start, CMTimeMake(10, 100)), CMTimeSubtract(timeRange.duration, CMTimeMake(10, 100))); - - NSDictionary *outputSettings = nil; - AVMutableComposition *composition = [AVMutableComposition composition]; - AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:avAsset composition:composition videoTrack:videoTrack preset:preset adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; - if (output == nil) - return false; - - AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:composition error:error]; - if (assetReader == nil) - return false; - - AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:error]; - if (assetWriter == nil) - return false; - - [assetReader addOutput:output]; - - AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSettings]; - - NSDictionary *sourcePixelBufferAttributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey, - [NSNumber numberWithInt:dimensions.width], kCVPixelBufferWidthKey, - [NSNumber numberWithInt:dimensions.height], kCVPixelBufferHeightKey, - nil]; - - AVAssetWriterInputPixelBufferAdaptor *pixelBufferInput = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:input sourcePixelBufferAttributes:sourcePixelBufferAttributesDictionary]; - - [assetWriter addInput:input]; - - videoProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; - - if (!inhibitAudio && [TGMediaVideoConversionPresetSettings keepAudioForPreset:preset] && audioTrack != nil) - { - AVMutableCompositionTrack *trimAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; - [trimAudioTrack insertTimeRange:timeRange ofTrack:audioTrack atTime:kCMTimeZero error:NULL]; - if (trimAudioTrack == nil) + if ([item isKindOfClass:[AVAsset class]]) { + TGMediaSampleBufferProcessor *videoProcessor = nil; + TGMediaSampleBufferProcessor *audioProcessor = nil; + + AVAsset *avAsset = (AVAsset *)item; + + AVAssetTrack *audioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] firstObject]; + AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; + if (videoTrack == nil) + return false; + + CGSize dimensions = CGSizeZero; + CMTimeRange timeRange = videoTrack.timeRange; + if (adjustments.trimApplied) + { + NSTimeInterval duration = CMTimeGetSeconds(videoTrack.timeRange.duration); + if (adjustments.trimEndValue < duration) + { + timeRange = adjustments.trimTimeRange; + } + else + { + timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC), CMTimeMakeWithSeconds(duration - adjustments.trimStartValue, NSEC_PER_SEC)); + } + } + timeRange = CMTimeRangeMake(CMTimeAdd(timeRange.start, CMTimeMake(10, 100)), CMTimeSubtract(timeRange.duration, CMTimeMake(10, 100))); + + NSDictionary *outputSettings = nil; + AVMutableComposition *composition = [AVMutableComposition composition]; + AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:avAsset composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; + if (output == nil) + return false; + + AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:composition error:error]; + if (assetReader == nil) + return false; + + AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:error]; + if (assetWriter == nil) return false; - AVAssetReaderOutput *output = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:trimAudioTrack outputSettings:@{ AVFormatIDKey: @(kAudioFormatLinearPCM) }]; [assetReader addOutput:output]; - AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[TGMediaVideoConversionPresetSettings audioSettingsForPreset:preset]]; + AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSettings]; [assetWriter addInput:input]; - audioProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; + videoProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; + + if (!inhibitAudio && [TGMediaVideoConversionPresetSettings keepAudioForPreset:preset] && audioTrack != nil) + { + AVMutableCompositionTrack *trimAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; + [trimAudioTrack insertTimeRange:timeRange ofTrack:audioTrack atTime:kCMTimeZero error:NULL]; + if (trimAudioTrack == nil) + return false; + + AVAssetReaderOutput *output = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:trimAudioTrack outputSettings:@{ AVFormatIDKey: @(kAudioFormatLinearPCM) }]; + [assetReader addOutput:output]; + + AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:[TGMediaVideoConversionPresetSettings audioSettingsForPreset:preset]]; + [assetWriter addInput:input]; + + audioProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; + } + + [outConversionContext modify:^id(TGMediaVideoConversionContext *currentContext) + { + return [currentContext contextWithAssetReader:assetReader assetWriter:assetWriter videoProcessor:videoProcessor audioProcessor:audioProcessor timeRange:timeRange dimensions:dimensions entityRenderer:entityRenderer]; + }]; + + return true; + } else if ([item isKindOfClass:[UIImage class]]) { + TGMediaSampleBufferProcessor *videoProcessor = nil; + + CGSize dimensions = CGSizeZero; + NSDictionary *outputSettings = nil; + CMTimeRange timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(0.0, NSEC_PER_SEC), CMTimeMakeWithSeconds(4.0, NSEC_PER_SEC)); + AVMutableComposition *composition = [AVMutableComposition composition]; + AVAssetTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:composition composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; + if (output == nil) + return false; + + AVAssetReader *assetReader = [[AVAssetReader alloc] initWithAsset:composition error:error]; + if (assetReader == nil) + return false; + + AVAssetWriter *assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:error]; + if (assetWriter == nil) + return false; + + [assetReader addOutput:output]; + + AVAssetWriterInput *input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSettings]; + [assetWriter addInput:input]; + + videoProcessor = [[TGMediaSampleBufferProcessor alloc] initWithAssetReaderOutput:output assetWriterInput:input]; + + [outConversionContext modify:^id(TGMediaVideoConversionContext *currentContext) + { + return [currentContext contextWithAssetReader:assetReader assetWriter:assetWriter videoProcessor:videoProcessor audioProcessor:nil timeRange:timeRange dimensions:dimensions entityRenderer:entityRenderer]; + }]; + + return true; } - - [outConversionContext modify:^id(TGMediaVideoConversionContext *currentContext) - { - return [currentContext contextWithAssetReader:assetReader assetWriter:assetWriter videoProcessor:videoProcessor audioProcessor:audioProcessor timeRange:timeRange dimensions:dimensions]; - }]; - - return true; + return false; } + (void)processWithConversionContext:(SAtomic *)context_ completionBlock:(void (^)(void))completionBlock @@ -865,6 +1040,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = _dimensions; context->_coverImage = _coverImage; context->_imageGenerator = _imageGenerator; + context->_entityRenderer = _entityRenderer; return context; } @@ -883,6 +1059,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = _dimensions; context->_coverImage = _coverImage; context->_imageGenerator = _imageGenerator; + context->_entityRenderer = _entityRenderer; return context; } @@ -900,6 +1077,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = _dimensions; context->_coverImage = _coverImage; context->_imageGenerator = imageGenerator; + context->_entityRenderer = _entityRenderer; return context; } @@ -917,10 +1095,11 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = _dimensions; context->_coverImage = coverImage; context->_imageGenerator = _imageGenerator; + context->_entityRenderer = _entityRenderer; return context; } -- (instancetype)contextWithAssetReader:(AVAssetReader *)assetReader assetWriter:(AVAssetWriter *)assetWriter videoProcessor:(TGMediaSampleBufferProcessor *)videoProcessor audioProcessor:(TGMediaSampleBufferProcessor *)audioProcessor timeRange:(CMTimeRange)timeRange dimensions:(CGSize)dimensions +- (instancetype)contextWithAssetReader:(AVAssetReader *)assetReader assetWriter:(AVAssetWriter *)assetWriter videoProcessor:(TGMediaSampleBufferProcessor *)videoProcessor audioProcessor:(TGMediaSampleBufferProcessor *)audioProcessor timeRange:(CMTimeRange)timeRange dimensions:(CGSize)dimensions entityRenderer:(id)entityRenderer { TGMediaVideoConversionContext *context = [[TGMediaVideoConversionContext alloc] init]; context->_queue = _queue; @@ -934,6 +1113,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, context->_dimensions = dimensions; context->_coverImage = _coverImage; context->_imageGenerator = _imageGenerator; + context->_entityRenderer = entityRenderer; return context; } diff --git a/submodules/LegacyComponents/Sources/TGMessageImageViewOverlayView.m b/submodules/LegacyComponents/Sources/TGMessageImageViewOverlayView.m index 52e88791cb..7d02043b76 100644 --- a/submodules/LegacyComponents/Sources/TGMessageImageViewOverlayView.m +++ b/submodules/LegacyComponents/Sources/TGMessageImageViewOverlayView.m @@ -702,7 +702,7 @@ const NSInteger TGMessageImageViewOverlayParticlesCount = 40; CGFloat offset = round(diameter * 0.06f); CGFloat verticalOffset = 0.0f; CGFloat alpha = 0.8f; - UIColor *iconColor = TGColorWithHexAndAlpha(0xff000000, 0.45f); + UIColor *iconColor = TGColorWithHexAndAlpha(0xffffffff, 1.0f); if (diameter <= 25.0f + FLT_EPSILON) { offset = round(50.0f * 0.06f) - 1.0f; verticalOffset += 0.5f; @@ -730,16 +730,11 @@ const NSInteger TGMessageImageViewOverlayParticlesCount = 40; } else { - CGContextSetFillColorWithColor(context, TGColorWithHexAndAlpha(0xffffffff, alpha).CGColor); + CGContextSetFillColorWithColor(context, TGColorWithHexAndAlpha(0x00000000, 0.3).CGColor); CGContextFillEllipseInRect(context, CGRectMake(0.0f, 0.0f, diameter, diameter)); - - CGContextBeginPath(context); - CGContextMoveToPoint(context, offset + floor((diameter - width) / 2.0f), verticalOffset + floor((diameter - height) / 2.0f)); - CGContextAddLineToPoint(context, offset + floor((diameter - width) / 2.0f) + width, verticalOffset + floor(diameter / 2.0f)); - CGContextAddLineToPoint(context, offset + floor((diameter - width) / 2.0f), verticalOffset + floor((diameter + height) / 2.0f)); - CGContextClosePath(context); - CGContextSetFillColorWithColor(context, iconColor.CGColor); - CGContextFillPath(context); + + UIImage *iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Play"], iconColor); + [iconImage drawAtPoint:CGPointMake(floor((diameter - iconImage.size.width) / 2.0f), floor((diameter - iconImage.size.height) / 2.0f)) blendMode:kCGBlendModeNormal alpha:1.0f]; } break; diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryZoomableItemView.m b/submodules/LegacyComponents/Sources/TGModernGalleryZoomableItemView.m index b72a4ee361..41c86a393f 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryZoomableItemView.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryZoomableItemView.m @@ -24,7 +24,7 @@ _containerView = [[TGModernGalleryImageItemContainerView alloc] initWithFrame:_internalContainerView.bounds]; [_internalContainerView addSubview:_containerView]; - _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:_containerView.bounds]; + _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:_containerView.bounds hasDoubleTap:true]; _scrollView.delegate = self; _scrollView.showsHorizontalScrollIndicator = false; _scrollView.showsVerticalScrollIndicator = false; diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryZoomableScrollView.m b/submodules/LegacyComponents/Sources/TGModernGalleryZoomableScrollView.m index 8b3a5bcc11..2eb54f6508 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryZoomableScrollView.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryZoomableScrollView.m @@ -3,25 +3,48 @@ #import "TGDoubleTapGestureRecognizer.h" @interface TGModernGalleryZoomableScrollView () - +{ + bool _hasDoubleTap; +} @end @implementation TGModernGalleryZoomableScrollView -- (instancetype)initWithFrame:(CGRect)frame +- (instancetype)initWithFrame:(CGRect)frame hasDoubleTap:(bool)hasDoubleTap { self = [super initWithFrame:frame]; if (self != nil) { - TGDoubleTapGestureRecognizer *recognizer = [[TGDoubleTapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapGesture:)]; - recognizer.consumeSingleTap = true; - [self addGestureRecognizer:recognizer]; + _hasDoubleTap = hasDoubleTap; + if (hasDoubleTap) { + TGDoubleTapGestureRecognizer *recognizer = [[TGDoubleTapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapGesture:)]; + recognizer.consumeSingleTap = true; + [self addGestureRecognizer:recognizer]; + } else { + self.panGestureRecognizer.minimumNumberOfTouches = 2; + } _normalZoomScale = 1.0f; } return self; } +- (void)setContentInset:(UIEdgeInsets)contentInset { + if (_hasDoubleTap) { + [super setContentInset:contentInset]; + } else { + [super setContentInset:UIEdgeInsetsZero]; + } +} + +- (UIEdgeInsets)adjustedContentInset { + if (_hasDoubleTap) { + return [super adjustedContentInset]; + } else { + return UIEdgeInsetsZero; + } +} + - (void)doubleTapGesture:(TGDoubleTapGestureRecognizer *)recognizer { if (recognizer.state == UIGestureRecognizerStateRecognized) diff --git a/submodules/LegacyComponents/Sources/TGPaintBrush.m b/submodules/LegacyComponents/Sources/TGPaintBrush.m index e492ed7ffd..67ca1ad9ce 100644 --- a/submodules/LegacyComponents/Sources/TGPaintBrush.m +++ b/submodules/LegacyComponents/Sources/TGPaintBrush.m @@ -2,7 +2,7 @@ #import -const CGSize TGPaintBrushTextureSize = { 256.0f, 256.0f }; +const CGSize TGPaintBrushTextureSize = { 384.0f, 384.0f }; const CGSize TGPaintBrushPreviewTextureSize = { 64.0f, 64.0f }; @interface TGPaintBrush () diff --git a/submodules/LegacyComponents/Sources/TGPaintCanvas.m b/submodules/LegacyComponents/Sources/TGPaintCanvas.m index 1ba27b520a..c750b4208e 100644 --- a/submodules/LegacyComponents/Sources/TGPaintCanvas.m +++ b/submodules/LegacyComponents/Sources/TGPaintCanvas.m @@ -22,6 +22,8 @@ CGAffineTransform _canvasTransform; CGRect _dirtyRect; + CGRect _visibleRect; + TGPaintInput *_input; TGPaintPanGestureRecognizer *_gestureRecognizer; bool _beganDrawing; @@ -40,6 +42,8 @@ if (self != nil) { self.contentScaleFactor = _screenScale; + self.multipleTouchEnabled = true; + self.exclusiveTouch = true; _state = [[TGPaintState alloc] init]; @@ -82,10 +86,18 @@ - (void)setFrame:(CGRect)frame { [super setFrame:frame]; + _visibleRect = self.bounds; [self _updateTransform]; } +- (void)setBounds:(CGRect)bounds +{ + [super setBounds:bounds]; + + _visibleRect = bounds; +} + - (void)_updateTransform { CGAffineTransform transform = CGAffineTransformIdentity; @@ -112,7 +124,7 @@ _gestureRecognizer = [[TGPaintPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; _gestureRecognizer.delegate = self; _gestureRecognizer.minimumNumberOfTouches = 1; - _gestureRecognizer.maximumNumberOfTouches = 1; + _gestureRecognizer.maximumNumberOfTouches = 2; __weak TGPaintCanvas *weakSelf = self; _gestureRecognizer.shouldRecognizeTap = ^bool @@ -162,7 +174,7 @@ } } -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)__unused gestureRecognizer +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (self.shouldDraw != nil) return self.shouldDraw(); @@ -172,9 +184,9 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { - if (gestureRecognizer == _gestureRecognizer && ([otherGestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UIRotationGestureRecognizer class]])) - return false; - +// if (gestureRecognizer == _gestureRecognizer && ([otherGestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UIRotationGestureRecognizer class]])) +// return false; +// return true; } @@ -264,16 +276,18 @@ - (CGRect)visibleRect { - return self.bounds; + return _visibleRect; } - (void)layoutSubviews { [super layoutSubviews]; - [_buffers update]; - - [self draw]; + [self.painting performSynchronouslyInContext:^{ + [_buffers update]; + + [self draw]; + }]; } #pragma mark - GL Setup diff --git a/submodules/LegacyComponents/Sources/TGPaintInput.m b/submodules/LegacyComponents/Sources/TGPaintInput.m index edd7dacfb0..ed38085d09 100644 --- a/submodules/LegacyComponents/Sources/TGPaintInput.m +++ b/submodules/LegacyComponents/Sources/TGPaintInput.m @@ -165,11 +165,13 @@ - (void)gestureCanceled:(UIGestureRecognizer *)recognizer { - TGPaintCanvas *canvas = (TGPaintCanvas *) recognizer.view; - TGPainting *painting = canvas.painting; + TGPaintCanvas *canvas = (TGPaintCanvas *) recognizer.view; + TGPainting *painting = canvas.painting; - painting.activePath = nil; - [canvas draw]; + [painting performAsynchronouslyInContext:^{ + painting.activePath = nil; + [canvas draw]; + }]; } - (void)paintPath:(TGPaintPath *)path inCanvas:(TGPaintCanvas *)canvas diff --git a/submodules/LegacyComponents/Sources/TGPaintPanGestureRecognizer.m b/submodules/LegacyComponents/Sources/TGPaintPanGestureRecognizer.m index 3686b96699..15fb86a144 100644 --- a/submodules/LegacyComponents/Sources/TGPaintPanGestureRecognizer.m +++ b/submodules/LegacyComponents/Sources/TGPaintPanGestureRecognizer.m @@ -16,7 +16,11 @@ - (void)touchesMoved:(NSSet *)inTouches withEvent:(UIEvent *)event { _touches = [inTouches copy]; - [super touchesMoved:inTouches withEvent:event]; + if (inTouches.count > 1) { + self.state = UIGestureRecognizerStateCancelled; + } else { + [super touchesMoved:inTouches withEvent:event]; + } } - (void)touchesEnded:(NSSet *)inTouches withEvent:(UIEvent *)event diff --git a/submodules/LegacyComponents/Sources/TGPaintRender.m b/submodules/LegacyComponents/Sources/TGPaintRender.m index 34718daf90..d23fc0adc2 100644 --- a/submodules/LegacyComponents/Sources/TGPaintRender.m +++ b/submodules/LegacyComponents/Sources/TGPaintRender.m @@ -168,7 +168,8 @@ typedef struct for (f = state.remainder; f <= distance; f += step, pressure += pressureStep) { CGFloat alpha = boldenFirst ? boldenedAlpha : state.alpha; - CGFloat brushSize = MIN(brushWeight, brushWeight - pressure * brushWeight * 0.55f); + CGFloat brushSize = brushWeight; +// CGFloat brushSize = MIN(brushWeight, brushWeight - pressure * brushWeight * 0.55f); [state addPoint:start size:brushSize angle:vectorAngle alpha:alpha index:i]; start = TGPaintAddPoints(start, TGPaintMultiplyPoint(unitVector, step)); diff --git a/submodules/LegacyComponents/Sources/TGPainting.h b/submodules/LegacyComponents/Sources/TGPainting.h index da659750fb..b2834dc781 100644 --- a/submodules/LegacyComponents/Sources/TGPainting.h +++ b/submodules/LegacyComponents/Sources/TGPainting.h @@ -27,6 +27,7 @@ - (instancetype)initWithSize:(CGSize)size undoManager:(TGPaintUndoManager *)undoManager imageData:(NSData *)imageData; +- (void)performSynchronouslyInContext:(void (^)(void))block; - (void)performAsynchronouslyInContext:(void (^)(void))block; - (void)paintStroke:(TGPaintPath *)path clearBuffer:(bool)clearBuffer completion:(void (^)(void))completion; diff --git a/submodules/LegacyComponents/Sources/TGPainting.m b/submodules/LegacyComponents/Sources/TGPainting.m index cce8fe48b3..bfc20bcd67 100644 --- a/submodules/LegacyComponents/Sources/TGPainting.m +++ b/submodules/LegacyComponents/Sources/TGPainting.m @@ -145,6 +145,15 @@ #pragma mark - +- (void)performSynchronouslyInContext:(void (^)(void))block +{ + [_queue dispatch:^ + { + [EAGLContext setCurrentContext:self.context]; + block(); + } synchronous:true]; +} + - (void)performAsynchronouslyInContext:(void (^)(void))block { [_queue dispatch:^ diff --git a/submodules/LegacyComponents/Sources/TGPaintingData.m b/submodules/LegacyComponents/Sources/TGPaintingData.m index 0f30568609..2cffd2f833 100644 --- a/submodules/LegacyComponents/Sources/TGPaintingData.m +++ b/submodules/LegacyComponents/Sources/TGPaintingData.m @@ -11,24 +11,34 @@ @interface TGPaintingData () { UIImage *_image; + UIImage *_stillImage; NSData *_data; UIImage *(^_imageRetrievalBlock)(void); + UIImage *(^_stillImageRetrievalBlock)(void); } @end @implementation TGPaintingData -+ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager ++ (instancetype)dataWithPaintingData:(NSData *)data image:(UIImage *)image stillImage:(UIImage *)stillImage entities:(NSArray *)entities undoManager:(TGPaintUndoManager *)undoManager { TGPaintingData *paintingData = [[TGPaintingData alloc] init]; paintingData->_data = data; paintingData->_image = image; + paintingData->_stillImage = stillImage; paintingData->_entities = entities; paintingData->_undoManager = undoManager; return paintingData; } ++ (instancetype)dataWithPaintingImagePath:(NSString *)imagePath entities:(NSArray *)entities { + TGPaintingData *paintingData = [[TGPaintingData alloc] init]; + paintingData->_imagePath = imagePath; + paintingData->_entities = entities; + return paintingData; +} + + (instancetype)dataWithPaintingImagePath:(NSString *)imagePath { TGPaintingData *paintingData = [[TGPaintingData alloc] init]; @@ -44,7 +54,7 @@ NSURL *imageUrl = nil; NSData *compressedData = TGPaintGZipDeflate(data.data); - [context setPaintingData:compressedData image:data.image forItem:item dataUrl:&dataUrl imageUrl:&imageUrl forVideo:video]; + [context setPaintingData:compressedData image:data.image stillImage:data.stillImage forItem:item dataUrl:&dataUrl imageUrl:&imageUrl forVideo:video]; __weak TGMediaEditingContext *weakContext = context; [[SQueue mainQueue] dispatch:^ @@ -61,6 +71,15 @@ return nil; }; + + data->_stillImageRetrievalBlock = ^UIImage * + { + __strong TGMediaEditingContext *strongContext = weakContext; + if (strongContext != nil) + return [strongContext stillPaintingImageForItem:item]; + + return nil; + }; }]; }]; } @@ -99,6 +118,16 @@ return nil; } +- (UIImage *)stillImage +{ + if (_stillImage != nil) + return _stillImage; + else if (_stillImageRetrievalBlock != nil) + return _stillImageRetrievalBlock(); + else + return nil; +} + - (NSArray *)stickers { NSMutableSet *stickers = [[NSMutableSet alloc] init]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index cb89ad2dfc..fbe55c5f8a 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -845,6 +845,7 @@ case TGPhotoEditorPaintTab: { TGPhotoPaintController *paintController = [[TGPhotoPaintController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView]; + paintController.stickersContext = _stickersContext; paintController.toolbarLandscapeSize = TGPhotoEditorToolbarSize; paintController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView, bool *noTransitionView) @@ -1408,12 +1409,12 @@ [_currentTabController prepareTransitionOutSaving:true]; TGPaintingData *paintingData = _photoEditor.paintingData; + bool saving = true; if ([_currentTabController isKindOfClass:[TGPhotoPaintController class]]) { TGPhotoPaintController *paintController = (TGPhotoPaintController *)_currentTabController; paintingData = [paintController paintingData]; - _photoEditor.paintingData = paintingData; if (paintingData != nil) @@ -1484,27 +1485,40 @@ UIImage *image = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); - CGSize thumbnailSize = TGPhotoThumbnailSizeForCurrentScreen(); - thumbnailSize.width = CGCeil(thumbnailSize.width); - thumbnailSize.height = CGCeil(thumbnailSize.height); - - CGSize fillSize = TGScaleToFillSize(videoDimensions, thumbnailSize); - - UIImage *thumbnailImage = nil; + 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)]; - if (adjustments.paintingData.image != nil) - [adjustments.paintingData.image 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(); - [_editingContext setImage:image thumbnailImage:thumbnailImage forItem:_item synchronous:true]; + [_editingContext setImage:fullImage thumbnailImage:thumbnailImage forItem:_item synchronous:true]; }]; }]; } diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m index 5d0c996697..4c0bf212de 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m @@ -23,6 +23,11 @@ return UIColorRGBA(0x000000, 0.7f); } ++ (UIColor *)toolbarIconColor +{ + return [UIColor whiteColor]; +} + + (UIColor *)accentColor { TGMediaAssetsPallete *pallete = nil; @@ -47,74 +52,69 @@ return UIColorRGB(0xd1d1d1); } -+ (UIImage *)captionIcon -{ - return TGComponentsImageNamed(@"PhotoEditorCaption.png"); -} - + (UIImage *)cropIcon { - return TGComponentsImageNamed(@"PhotoEditorCrop.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Crop"], [self toolbarIconColor]); } + (UIImage *)toolsIcon { - return TGComponentsImageNamed(@"PhotoEditorTools.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Adjustments"], [self toolbarIconColor]); } + (UIImage *)rotateIcon { - return TGComponentsImageNamed(@"PhotoEditorRotateIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Rotate"], [self toolbarIconColor]); } + (UIImage *)paintIcon { - return TGComponentsImageNamed(@"PhotoEditorPaint.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Drawing"], [self toolbarIconColor]); } + (UIImage *)stickerIcon { - return TGComponentsImageNamed(@"PaintStickersIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/AddSticker"], [self toolbarIconColor]); } + (UIImage *)textIcon { - return TGComponentsImageNamed(@"PaintTextIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/AddText"], [self toolbarIconColor]); } + (UIImage *)eraserIcon { - return TGComponentsImageNamed(@"PaintEraserIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Eraser"], [self toolbarIconColor]); } + (UIImage *)mirrorIcon { - return TGComponentsImageNamed(@"PhotoEditorMirrorIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Flip"], [self toolbarIconColor]); } + (UIImage *)aspectRatioIcon { - return TGComponentsImageNamed(@"PhotoEditorAspectRatioIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/AspectRatio"], [self toolbarIconColor]); } + (UIImage *)aspectRatioActiveIcon { - return TGTintedImage(TGComponentsImageNamed(@"PhotoEditorAspectRatioIcon.png"), [self accentColor]); + return TGTintedImage([UIImage imageNamed:@"Editor/AspectRatio"], [self accentColor]); } + (UIImage *)tintIcon { - return TGComponentsImageNamed(@"PhotoEditorTintIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Tint"], [self toolbarIconColor]); } + (UIImage *)blurIcon { - return TGComponentsImageNamed(@"PhotoEditorBlurIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Blur"], [self toolbarIconColor]); } + (UIImage *)curvesIcon { - return TGComponentsImageNamed(@"PhotoEditorCurvesIcon.png"); + return TGTintedImage([UIImage imageNamed:@"Editor/Curves"], [self toolbarIconColor]); } + (UIImage *)gifBackgroundImage @@ -207,9 +207,13 @@ + (UIImage *)qualityIconForPreset:(TGMediaVideoConversionPreset)preset { - UIImage *background = TGComponentsImageNamed(@"PhotoEditorQuality"); + CGSize size = CGSizeMake(27.0f, 22.0f); + CGRect rect = CGRectInset(CGRectMake(0.0f, 0.0f, size.width, size.height), 1.0, 1.0); + UIGraphicsBeginImageContextWithOptions(size, false, 0.0f); - UIGraphicsBeginImageContextWithOptions(background.size, false, 0.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:5.0f]; NSString *label = @""; switch (preset) @@ -239,12 +243,15 @@ break; } - [background drawAtPoint:CGPointZero]; - + CGContextAddPath(context, path.CGPath); + CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); + CGContextSetLineWidth(context, 2.0f - TGScreenPixel); + CGContextStrokePath(context); + UIFont *font = [TGFont roundedFontOfSize:11]; - CGSize size = [label sizeWithFont:font]; + CGSize textSize = [label sizeWithFont:font]; [[UIColor whiteColor] setFill]; - [label drawInRect:CGRectMake(floor(background.size.width - size.width) / 2.0f, 8.0f, size.width, size.height) withFont:font]; + [label drawInRect:CGRectMake(floor(size.width - textSize.width) / 2.0f, 4.0f, textSize.width, textSize.height) withFont:font]; UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m b/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m index bb5567cc99..ad373bcab0 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m @@ -172,7 +172,7 @@ { [self performTransitionInWithCompletion:nil]; } - else if (_delayedImage != nil) + else if (_delayedImage != nil && [_delayedImage isKindOfClass:[UIImage class]]) { UIImageView *transitionView = [[UIImageView alloc] initWithFrame:_snapshotView.frame]; transitionView.image = ((UIImageView *)_snapshotView).image; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.h b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.h index 6d321c5829..fa0909d874 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.h +++ b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.h @@ -15,7 +15,7 @@ - (void)handlePinch:(UIPinchGestureRecognizer *)gestureRecognizer; - (void)handleRotate:(UIRotationGestureRecognizer *)gestureRecognizer; -- (UIImage *)imageInRect:(CGRect)rect background:(UIImage *)background; +- (UIImage *)imageInRect:(CGRect)rect background:(UIImage *)background still:(bool)still; - (bool)isTrackingAnyEntityView; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m index 242bc35188..f10718f5d0 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEntitiesContainerView.m @@ -219,7 +219,7 @@ return nil; } -- (UIImage *)imageInRect:(CGRect)rect background:(UIImage *)background +- (UIImage *)imageInRect:(CGRect)rect background:(UIImage *)background still:(bool)still { if (self.subviews.count < 2) return nil; @@ -241,13 +241,15 @@ { TGPhotoStickerEntityView *stickerView = (TGPhotoStickerEntityView *)view; UIImage *image = stickerView.image; - CGSize fittedSize = TGScaleToSize(image.size, view.bounds.size); - - CGContextTranslateCTM(context, view.bounds.size.width / 2.0f, view.bounds.size.height / 2.0f); - if (stickerView.isMirrored) - CGContextScaleCTM(context, -1, 1); - - [image drawInRect:CGRectMake(-fittedSize.width / 2.0f, -fittedSize.height / 2.0f, fittedSize.width, fittedSize.height)]; + if (image != nil) { + CGSize fittedSize = TGScaleToSize(image.size, view.bounds.size); + + CGContextTranslateCTM(context, view.bounds.size.width / 2.0f, view.bounds.size.height / 2.0f); + if (stickerView.isMirrored) + CGContextScaleCTM(context, -1, 1); + + [image drawInRect:CGRectMake(-fittedSize.width / 2.0f, -fittedSize.height / 2.0f, fittedSize.width, fittedSize.height)]; + } }]; } else if ([view isKindOfClass:[TGPhotoTextEntityView class]]) diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintActionsView.m b/submodules/LegacyComponents/Sources/TGPhotoPaintActionsView.m index 1ea991476d..8b3ca16860 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintActionsView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintActionsView.m @@ -2,6 +2,7 @@ #import "LegacyComponentsInternal.h" #import "TGFont.h" +#import "TGImageUtils.h" #import @@ -24,7 +25,7 @@ _undoButton.adjustsImageWhenDisabled = false; _undoButton.enabled = false; _undoButton.exclusiveTouch = true; - [_undoButton setImage:TGComponentsImageNamed(@"PaintUndoIcon") forState:UIControlStateNormal]; + [_undoButton setImage:TGTintedImage([UIImage imageNamed:@"Editor/Undo"], [UIColor whiteColor]) forState:UIControlStateNormal]; [_undoButton addTarget:self action:@selector(undoButtonPressed) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:_undoButton]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.h b/submodules/LegacyComponents/Sources/TGPhotoPaintController.h index d343bc4453..613341b8b2 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.h +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.h @@ -5,8 +5,12 @@ @class PGPhotoEditor; @class TGPhotoEditorPreviewView; +@protocol TGPhotoPaintStickersContext; + @interface TGPhotoPaintController : TGPhotoEditorTabController +@property (nonatomic, strong) id stickersContext; + - (instancetype)initWithContext:(id)context photoEditor:(PGPhotoEditor *)photoEditor previewView:(TGPhotoEditorPreviewView *)previewView; - (TGPaintingData *)paintingData; diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m index e8a25a309f..3e808558a6 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintController.m @@ -12,7 +12,6 @@ #import #import -#import #import "TGMenuSheetController.h" @@ -28,6 +27,8 @@ #import "TGPaintingWrapperView.h" #import "TGPaintState.h" #import "TGPaintBrushPreview.h" +#import "TGPaintSwatch.h" +#import "TGPhotoPaintFont.h" #import #import "PGPhotoEditor.h" @@ -53,7 +54,7 @@ const CGFloat TGPhotoPaintTopPanelSize = 44.0f; const CGFloat TGPhotoPaintBottomPanelSize = 79.0f; const CGSize TGPhotoPaintingLightMaxSize = { 1280.0f, 1280.0f }; -const CGSize TGPhotoPaintingMaxSize = { 1600.0f, 1600.0f }; +const CGSize TGPhotoPaintingMaxSize = { 1920.0f, 1920.0f }; const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; @@ -63,6 +64,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; TGObserverProxy *_keyboardWillChangeFrameProxy; CGFloat _keyboardHeight; + TGModernGalleryZoomableScrollView *_scrollView; + UIView *_scrollContentView; + UIButton *_containerView; TGPhotoPaintSparseView *_wrapperView; UIView *_portraitToolsWrapperView; @@ -76,7 +80,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; TGPaintCanvas *_canvasView; TGPaintBrushPreview *_brushPreview; - UIView *_scrollView; + CGSize _previousSize; + UIView *_contentView; UIView *_contentWrapperView; UIView *_dimView; @@ -110,14 +115,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; TGMenuContainerView *_menuContainerView; TGPaintingData *_resultData; - - AVPlayer *_player; - SMetaDisposable *_playerItemDisposable; - id _playerStartedObserver; - id _playerReachedEndObserver; - + UIImage *_stillImage; + TGPaintingWrapperView *_paintingWrapperView; - TGModernGalleryVideoView *_videoView; SMetaDisposable *_faceDetectorDisposable; NSArray *_faces; @@ -176,18 +176,30 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; { [_actionHandle reset]; [_faceDetectorDisposable dispose]; - [_playerItemDisposable dispose]; } - (void)loadView { [super loadView]; self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + _scrollView = [[TGModernGalleryZoomableScrollView alloc] initWithFrame:self.view.bounds hasDoubleTap:false]; + if (iosMajorVersion() >= 11) { + _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + _scrollView.contentInset = UIEdgeInsetsZero; + _scrollView.delegate = self; + _scrollView.showsHorizontalScrollIndicator = false; + _scrollView.showsVerticalScrollIndicator = false; + [self.view addSubview:_scrollView]; + + _scrollContentView = [[UIView alloc] initWithFrame:self.view.bounds]; + [_scrollView addSubview:_scrollContentView]; _containerView = [[UIButton alloc] initWithFrame:self.view.bounds]; _containerView.clipsToBounds = true; [_containerView addTarget:self action:@selector(containerPressed) forControlEvents:UIControlEventTouchUpInside]; - [self.view addSubview:_containerView]; + [_scrollContentView addSubview:_containerView]; _pinchGestureRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)]; _pinchGestureRecognizer.delegate = self; @@ -215,14 +227,14 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; }; [_containerView addSubview:_paintingWrapperView]; - _scrollView = [[UIView alloc] init]; - _scrollView.clipsToBounds = true; - _scrollView.userInteractionEnabled = false; - [_containerView addSubview:_scrollView]; + _contentView = [[UIView alloc] init]; + _contentView.clipsToBounds = true; + _contentView.userInteractionEnabled = false; + [_containerView addSubview:_contentView]; _contentWrapperView = [[UIView alloc] init]; _contentWrapperView.userInteractionEnabled = false; - [_scrollView addSubview:_contentWrapperView]; + [_contentView addSubview:_contentWrapperView]; _entitiesContainerView = [[TGPhotoEntitiesContainerView alloc] init]; _entitiesContainerView.clipsToBounds = true; @@ -591,6 +603,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (void)_clearCurrentSelection { + _scrollView.pinchGestureRecognizer.enabled = true; _currentEntityView = nil; if (_entitySelectionView != nil) { @@ -622,6 +635,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; UIImage *image = _painting.isEmpty ? nil : [_painting imageWithSize:fittedSize andData:&data]; NSMutableArray *entities = [[NSMutableArray alloc] init]; + bool hasAnimatedEntities = false; + UIImage *stillImage = nil; if (image == nil && _entitiesContainerView.entitiesCount < 1) { _resultData = nil; @@ -629,20 +644,46 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; } else if (_entitiesContainerView.entitiesCount > 0) { - image = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image]; - for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) { if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) continue; TGPhotoPaintEntity *entity = [view entity]; - if (entity != nil) + if (entity != nil) { + if (entity.animated) { + hasAnimatedEntities = true; + } [entities addObject:entity]; + } + } + + if (hasAnimatedEntities) { + for (TGPhotoPaintEntity *entity in entities) { + if ([entity isKindOfClass:[TGPhotoPaintTextEntity class]]) { + TGPhotoPaintTextEntity *textEntity = (TGPhotoPaintTextEntity *)entity; + for (TGPhotoPaintEntityView *view in _entitiesContainerView.subviews) + { + if (![view isKindOfClass:[TGPhotoPaintEntityView class]]) + continue; + + if (view.entityUUID == textEntity.uuid) { + textEntity.renderImage = [(TGPhotoTextEntityView *)view image]; + break; + } + } + } + } + } + + if (!hasAnimatedEntities) { + image = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image still:false]; + } else { + stillImage = [_entitiesContainerView imageInRect:_entitiesContainerView.bounds background:image still:true]; } } - _resultData = [TGPaintingData dataWithPaintingData:data image:image entities:entities undoManager:_undoManager]; + _resultData = [TGPaintingData dataWithPaintingData:data image:image stillImage:stillImage entities:entities undoManager:_undoManager]; return _resultData; } @@ -678,6 +719,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; _currentEntityView = view; [self updateSettingsButton]; + _scrollView.pinchGestureRecognizer.enabled = _currentEntityView == nil; + if (view != nil) { [_currentEntityView.superview bringSubviewToFront:_currentEntityView]; @@ -946,7 +989,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (TGPhotoStickerEntityView *)_createStickerViewWithEntity:(TGPhotoPaintStickerEntity *)entity { - TGPhotoStickerEntityView *stickerView = [[TGPhotoStickerEntityView alloc] initWithEntity:entity]; + TGPhotoStickerEntityView *stickerView = [[TGPhotoStickerEntityView alloc] initWithEntity:entity context:self.stickersContext]; [self _commonEntityViewSetup:stickerView entity:entity]; [_entitiesContainerView addSubview:stickerView]; @@ -987,61 +1030,70 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (void)presentStickersView { - if (_stickersView == nil) - { - TGPhotoStickersView *view = [[TGPhotoStickersView alloc] initWithContext:_context frame:self.view.bounds]; - view.parentViewController = self; - - __weak TGPhotoPaintController *weakSelf = self; - __weak TGPhotoStickersView *weakStickersView = view; - view.stickerSelected = ^(TGDocumentMediaAttachment *document, CGPoint transitionPoint, TGPhotoStickersView *stickersView, UIView *snapshotView) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf createNewStickerWithDocument:document transitionPoint:transitionPoint stickersView:stickersView snapshotView:snapshotView]; - }; - view.dismissed = ^ - { - __strong TGPhotoStickersView *strongStickersView = weakStickersView; - if (strongStickersView != nil) - [strongStickersView removeFromSuperview]; - }; - - _stickersView = view; - } + __weak TGPhotoPaintController *weakSelf = self; + _stickersContext.presentStickersController(^(id document, bool animated, UIView *view, CGRect rect) { + __strong TGPhotoPaintController *strongSelf = weakSelf; + if (strongSelf != nil) { + UIView *snapshot = [view snapshotViewAfterScreenUpdates:false]; + [strongSelf createNewStickerWithDocument:document animated:animated transitionPoint:CGPointZero stickersView:nil snapshotView:nil]; + } + }); - if ([_context currentSizeClass] == UIUserInterfaceSizeClassCompact) - { - _stickersView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _stickersView.frame = self.view.bounds; - _stickersView.safeAreaInset = self.controllerSafeAreaInset; - [self.parentViewController.view addSubview:_stickersView]; - } - else - { - _settingsView = _stickersView; - [_stickersView sizeToFit]; - - UIView *wrapper = [self settingsViewWrapper]; - wrapper.userInteractionEnabled = true; - [wrapper addSubview:_stickersView]; - - [self viewWillLayoutSubviews]; - } - - _stickersView.outerView = self.parentViewController.view; - _stickersView.targetView = _contentWrapperView; - - [_stickersView present]; +// if (_stickersView == nil) +// { +// TGPhotoStickersView *view = [[TGPhotoStickersView alloc] initWithContext:_context frame:self.view.bounds]; +// view.parentViewController = self; +// +// __weak TGPhotoPaintController *weakSelf = self; +// __weak TGPhotoStickersView *weakStickersView = view; +// view.stickerSelected = ^(TGDocumentMediaAttachment *document, CGPoint transitionPoint, TGPhotoStickersView *stickersView, UIView *snapshotView) +// { +// __strong TGPhotoPaintController *strongSelf = weakSelf; +// if (strongSelf != nil) +// [strongSelf createNewStickerWithDocument:document transitionPoint:transitionPoint stickersView:stickersView snapshotView:snapshotView]; +// }; +// view.dismissed = ^ +// { +// __strong TGPhotoStickersView *strongStickersView = weakStickersView; +// if (strongStickersView != nil) +// [strongStickersView removeFromSuperview]; +// }; +// +// _stickersView = view; +// } +// +// if ([_context currentSizeClass] == UIUserInterfaceSizeClassCompact) +// { +// _stickersView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; +// _stickersView.frame = self.view.bounds; +// _stickersView.safeAreaInset = self.controllerSafeAreaInset; +// [self.parentViewController.view addSubview:_stickersView]; +// } +// else +// { +// _settingsView = _stickersView; +// [_stickersView sizeToFit]; +// +// UIView *wrapper = [self settingsViewWrapper]; +// wrapper.userInteractionEnabled = true; +// [wrapper addSubview:_stickersView]; +// +// [self viewWillLayoutSubviews]; +// } +// +// _stickersView.outerView = self.parentViewController.view; +// _stickersView.targetView = _contentWrapperView; +// +// [_stickersView present]; } -- (void)createNewStickerWithDocument:(TGDocumentMediaAttachment *)document transitionPoint:(CGPoint)transitionPoint stickersView:(TGPhotoStickersView *)stickersView snapshotView:(UIView *)snapshotView +- (void)createNewStickerWithDocument:(id)document animated:(bool)animated transitionPoint:(CGPoint)transitionPoint stickersView:(TGPhotoStickersView *)stickersView snapshotView:(UIView *)snapshotView { - TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:document baseSize:[self _stickerBaseSizeForCurrentPainting]]; + TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:document baseSize:[self _stickerBaseSizeForCurrentPainting] animated:animated]; [self _setStickerEntityPosition:entity]; TGPhotoStickerEntityView *stickerView = [self _createStickerViewWithEntity:entity]; - stickerView.hidden = true; +// stickerView.hidden = true; CGFloat rotation = entity.angle - [self startRotation]; @@ -1059,6 +1111,8 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; [self selectEntityView:stickerView]; _entitySelectionView.alpha = 0.0f; + [_entitySelectionView fadeIn]; + [self _registerEntityRemovalUndo:entity]; [self updateActionsView]; } @@ -1118,7 +1172,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; _dimView.alpha = 1.0f; }; - _scrollView.userInteractionEnabled = true; + _contentView.userInteractionEnabled = true; _contentWrapperView.userInteractionEnabled = true; if (iosMajorVersion() >= 7) @@ -1141,7 +1195,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (void)sendTextEntityViewBack { - _scrollView.userInteractionEnabled = false; + _contentView.userInteractionEnabled = false; _contentWrapperView.userInteractionEnabled = false; _dimView.userInteractionEnabled = false; @@ -1226,7 +1280,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (CGFloat)_brushBaseWeightForCurrentPainting { - return 25.0f / TGPhotoPaintingMaxSize.width * _painting.size.width; + return 15.0f / TGPhotoPaintingMaxSize.width * _painting.size.width; } - (CGFloat)_brushWeightRangeForCurrentPainting @@ -1489,14 +1543,106 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (void)resetScrollView { - CGSize contentSize = [self fittedContentSize]; + CGSize fittedContentSize = [self fittedContentSize]; CGRect fittedCropRect = [self fittedCropRect:false]; + _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, fittedContentSize.width, fittedContentSize.height); - _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height); - - CGFloat scale = _scrollView.bounds.size.width / fittedCropRect.size.width; + CGFloat scale = _contentView.bounds.size.width / fittedCropRect.size.width; _contentWrapperView.transform = CGAffineTransformMakeScale(scale, scale); - _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _scrollView.bounds.size.width, _scrollView.bounds.size.height); + _contentWrapperView.frame = CGRectMake(0.0f, 0.0f, _contentView.bounds.size.width, _contentView.bounds.size.height); + + CGSize contentSize = [self contentSize]; + _scrollView.minimumZoomScale = 1.0f; + _scrollView.maximumZoomScale = 1.0f; + _scrollView.normalZoomScale = 1.0f; + _scrollView.zoomScale = 1.0f; + _scrollView.contentSize = contentSize; + [self contentView].frame = CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height); + + [self adjustZoom]; + _scrollView.zoomScale = _scrollView.normalZoomScale; +} + +- (void)scrollViewWillBeginZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view +{ +} + +- (void)scrollViewDidZoom:(UIScrollView *)__unused scrollView +{ + [self adjustZoom]; +} + +- (void)scrollViewDidEndZooming:(UIScrollView *)__unused scrollView withView:(UIView *)__unused view atScale:(CGFloat)__unused scale +{ + [self adjustZoom]; + + if (_scrollView.zoomScale < _scrollView.normalZoomScale - FLT_EPSILON) + { + [TGHacks setAnimationDurationFactor:0.5f]; + [_scrollView setZoomScale:_scrollView.normalZoomScale animated:true]; + [TGHacks setAnimationDurationFactor:1.0f]; + } +} + +- (UIView *)contentView +{ + return _scrollContentView; +} + +- (CGSize)contentSize +{ + return _scrollView.frame.size; +} + +- (UIView *)viewForZoomingInScrollView:(UIScrollView *)__unused scrollView +{ + return [self contentView]; +} + +- (void)adjustZoom +{ + CGSize contentSize = [self contentSize]; + CGSize boundsSize = _scrollView.frame.size; + if (contentSize.width < FLT_EPSILON || contentSize.height < FLT_EPSILON || boundsSize.width < FLT_EPSILON || boundsSize.height < FLT_EPSILON) + return; + + CGFloat scaleWidth = boundsSize.width / contentSize.width; + CGFloat scaleHeight = boundsSize.height / contentSize.height; + CGFloat minScale = MIN(scaleWidth, scaleHeight); + CGFloat maxScale = MAX(scaleWidth, scaleHeight); + maxScale = MAX(maxScale, minScale * 3.0f); + + if (ABS(maxScale - minScale) < 0.01f) + maxScale = minScale; + + + if (iosMajorVersion() >= 11) { + _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; + } + _scrollView.contentInset = UIEdgeInsetsZero; + + if (_scrollView.minimumZoomScale != 0.05f) + _scrollView.minimumZoomScale = 0.05f; + if (_scrollView.normalZoomScale != minScale) + _scrollView.normalZoomScale = minScale; + if (_scrollView.maximumZoomScale != maxScale) + _scrollView.maximumZoomScale = maxScale; + + CGRect contentFrame = [self contentView].frame; + + if (boundsSize.width > contentFrame.size.width) + contentFrame.origin.x = (boundsSize.width - contentFrame.size.width) / 2.0f; + else + contentFrame.origin.x = 0; + + if (boundsSize.height > contentFrame.size.height) + contentFrame.origin.y = (boundsSize.height - contentFrame.size.height) / 2.0f; + else + contentFrame.origin.y = 0; + + [self contentView].frame = contentFrame; + + _scrollView.scrollEnabled = ABS(_scrollView.zoomScale - _scrollView.normalZoomScale) > FLT_EPSILON; } #pragma mark - Gestures @@ -1513,6 +1659,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)__unused gestureRecognizer { + if (gestureRecognizer == _pinchGestureRecognizer && _currentEntityView == nil) { + return false; + } return !_canvasView.isTracking; } @@ -1615,15 +1764,13 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; [_contentWrapperView addSubview:_entitiesContainerView]; [self resetScrollView]; - - [self setupVideoPlaybackIfNeeded]; } - (void)prepareForCustomTransitionOut { _previewView.hidden = true; _canvasView.hidden = true; - _scrollView.hidden = true; + _contentView.hidden = true; [UIView animateWithDuration:0.3f animations:^ { _portraitToolsWrapperView.alpha = 0.0f; @@ -1651,8 +1798,6 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; if (completion != nil) completion(); }]; - - [_player pause]; } - (CGRect)transitionOutSourceFrameForReferenceFrame:(CGRect)referenceFrame orientation:(UIInterfaceOrientation)orientation @@ -1701,13 +1846,13 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; UIView *canvasSnapshotView = [_paintingWrapperView resizableSnapshotViewFromRect:[_paintingWrapperView convertRect:previewView.bounds fromView:previewView] afterScreenUpdates:false withCapInsets:UIEdgeInsetsZero]; canvasSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - canvasSnapshotView.transform = _scrollView.transform; + canvasSnapshotView.transform = _contentView.transform; canvasSnapshotView.frame = snapshotView.bounds; [snapshotView addSubview:canvasSnapshotView]; UIView *entitiesSnapshotView = [_contentWrapperView resizableSnapshotViewFromRect:[_contentWrapperView convertRect:previewView.bounds fromView:previewView] afterScreenUpdates:false withCapInsets:UIEdgeInsetsZero]; entitiesSnapshotView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - entitiesSnapshotView.transform = _scrollView.transform; + entitiesSnapshotView.transform = _contentView.transform; entitiesSnapshotView.frame = snapshotView.bounds; [snapshotView addSubview:entitiesSnapshotView]; @@ -1728,21 +1873,19 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; POPSpringAnimation *previewAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame]; previewAnimation.fromValue = [NSValue valueWithCGRect:previewView.frame]; previewAnimation.toValue = [NSValue valueWithCGRect:targetFrame]; - if (_videoView == nil) - [animations addObject:previewAnimation]; + [animations addObject:previewAnimation]; POPSpringAnimation *previewAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha]; previewAlphaAnimation.fromValue = @(previewView.alpha); previewAlphaAnimation.toValue = @(0.0f); - if (_videoView == nil) - [animations addObject:previewAnimation]; + [animations addObject:previewAnimation]; POPSpringAnimation *entitiesAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewCenter]; - entitiesAnimation.fromValue = [NSValue valueWithCGPoint:_scrollView.center]; + entitiesAnimation.fromValue = [NSValue valueWithCGPoint:_contentView.center]; entitiesAnimation.toValue = [NSValue valueWithCGPoint:targetCenter]; [animations addObject:entitiesAnimation]; - CGFloat targetEntitiesScale = targetFrame.size.width / _scrollView.frame.size.width; + CGFloat targetEntitiesScale = targetFrame.size.width / _contentView.frame.size.width; POPSpringAnimation *entitiesScaleAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewScaleXY]; entitiesScaleAnimation.fromValue = [NSValue valueWithCGSize:CGSizeMake(1.0f, 1.0f)]; entitiesScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(targetEntitiesScale, targetEntitiesScale)]; @@ -1782,9 +1925,9 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; [previewView pop_addAnimation:previewAnimation forKey:@"frame"]; [previewView pop_addAnimation:previewAlphaAnimation forKey:@"alpha"]; - [_scrollView pop_addAnimation:entitiesAnimation forKey:@"frame"]; - [_scrollView pop_addAnimation:entitiesScaleAnimation forKey:@"scale"]; - [_scrollView pop_addAnimation:entitiesAlphaAnimation forKey:@"alpha"]; + [_contentView pop_addAnimation:entitiesAnimation forKey:@"frame"]; + [_contentView pop_addAnimation:entitiesScaleAnimation forKey:@"scale"]; + [_contentView pop_addAnimation:entitiesAlphaAnimation forKey:@"alpha"]; [_paintingWrapperView pop_addAnimation:paintingAnimation forKey:@"frame"]; [_paintingWrapperView pop_addAnimation:paintingScaleAnimation forKey:@"scale"]; @@ -1792,7 +1935,7 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; if (saving) { - _scrollView.hidden = true; + _contentView.hidden = true; _paintingWrapperView.hidden = true; previewView.hidden = true; } @@ -1902,10 +2045,16 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; _landscapeToolsWrapperView.hidden = true; orientation = UIInterfaceOrientationPortrait; } - + CGSize referenceSize = [self referenceViewSize]; CGFloat screenSide = MAX(referenceSize.width, referenceSize.height) + 2 * TGPhotoPaintBottomPanelSize; + bool sizeUpdated = false; + if (!CGSizeEqualToSize(referenceSize, _previousSize)) { + sizeUpdated = true; + _previousSize = referenceSize; + } + CGFloat panelToolbarPortraitSize = TGPhotoPaintBottomPanelSize + TGPhotoEditorToolbarSize; CGFloat panelToolbarLandscapeSize = TGPhotoPaintBottomPanelSize + self.toolbarLandscapeSize; @@ -2035,9 +2184,15 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; CGFloat rotation = _photoEditor.cropRotation; CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(TGRotationForOrientation(cropOrientation)); - _scrollView.transform = rotationTransform; - _scrollView.frame = previewFrame; - [self resetScrollView]; + _contentView.transform = rotationTransform; + _contentView.frame = previewFrame; + + _scrollView.frame = self.view.bounds; + + if (sizeUpdated) { + [self resetScrollView]; + } + [self adjustZoom]; _paintingWrapperView.transform = CGAffineTransformMakeRotation(TGRotationForOrientation(cropOrientation)); _paintingWrapperView.frame = previewFrame; @@ -2063,7 +2218,6 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; _selectionContainerView.transform = CGAffineTransformRotate(rotationTransform, rotation); _selectionContainerView.frame = previewFrame; - _videoView.frame = originalFrame; _containerView.frame = CGRectMake(containerFrame.origin.x, containerFrame.origin.y + offsetHeight, containerFrame.size.width, containerFrame.size.height); } @@ -2113,104 +2267,20 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f; } completion:nil]; } -#pragma mark - Video Playback - -- (void)setupVideoPlaybackIfNeeded -{ - if ((![self.item isKindOfClass:[TGMediaAsset class]] || !((TGMediaAsset *)self.item).isVideo) && ![self.item isKindOfClass:[AVAsset class]]) - return; - - SSignal *itemSignal = [self.item isKindOfClass:[TGMediaAsset class]] ? [TGMediaAssetImageSignals playerItemForVideoAsset:(TGMediaAsset *)self.item] : [SSignal single:[AVPlayerItem playerItemWithAsset:((AVAsset *)self.item)]]; - ; - - __weak TGPhotoPaintController *weakSelf = self; - [_playerItemDisposable setDisposable:[[itemSignal deliverOn:[SQueue mainQueue]] startWithNext:^(AVPlayerItem *playerItem) - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf == nil) - return; - - strongSelf->_player = [AVPlayer playerWithPlayerItem:playerItem]; - strongSelf->_player.actionAtItemEnd = AVPlayerActionAtItemEndNone; - strongSelf->_player.muted = true; - - NSTimeInterval startPosition = 0.0f; - if (strongSelf->_photoEditor.trimStartValue > DBL_EPSILON) - startPosition = strongSelf->_photoEditor.trimStartValue; - - CMTime targetTime = CMTimeMakeWithSeconds(startPosition, NSEC_PER_SEC); - [strongSelf->_player.currentItem seekToTime:targetTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero]; - - [strongSelf _setupPlaybackStartedObserver]; - - strongSelf->_videoView = [[TGModernGalleryVideoView alloc] initWithFrame:strongSelf->_previewView.frame player:strongSelf->_player]; - strongSelf->_videoView.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; - strongSelf->_videoView.playerLayer.opaque = false; - strongSelf->_videoView.playerLayer.backgroundColor = nil; - [strongSelf->_paintingWrapperView insertSubview:strongSelf->_videoView atIndex:0]; - - [strongSelf->_player play]; - - [strongSelf updateLayout:strongSelf.interfaceOrientation]; - }]]; -} - -- (void)_setupPlaybackStartedObserver -{ - CMTime startTime = CMTimeMake(10, 100); - if (_photoEditor.trimStartValue > DBL_EPSILON) - startTime = CMTimeMakeWithSeconds(_photoEditor.trimStartValue + 0.1, NSEC_PER_SEC); - - __weak TGPhotoPaintController *weakSelf = self; - _playerStartedObserver = [_player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:startTime]] queue:NULL usingBlock:^ - { - __strong TGPhotoPaintController *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 -{ - CMTime endTime = CMTimeSubtract(_player.currentItem.duration, CMTimeMake(10, 100)); - if (_photoEditor.trimEndValue > DBL_EPSILON && _photoEditor.trimEndValue < CMTimeGetSeconds(_player.currentItem.duration)) - endTime = CMTimeMakeWithSeconds(_photoEditor.trimEndValue - 0.1, NSEC_PER_SEC); - - CMTime startTime = CMTimeMake(5, 100); - if (_photoEditor.trimStartValue > DBL_EPSILON) - startTime = CMTimeMakeWithSeconds(_photoEditor.trimStartValue + 0.05, NSEC_PER_SEC); - - __weak TGPhotoPaintController *weakSelf = self; - _playerReachedEndObserver = [_player addBoundaryTimeObserverForTimes:@[[NSValue valueWithCMTime:endTime]] queue:NULL usingBlock:^ - { - __strong TGPhotoPaintController *strongSelf = weakSelf; - if (strongSelf != nil) - [strongSelf->_player seekToTime:startTime]; - }]; -} - -#pragma mark - Face Detection - - (void)_setStickerEntityPosition:(TGPhotoPaintStickerEntity *)entity { TGDocumentMediaAttachment *document = entity.document; TGDocumentAttributeSticker *sticker = nil; - for (id attribute in document.attributes) - { - if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) - { - sticker = (TGDocumentAttributeSticker *)attribute; - break; - } - } +// for (id attribute in document.attributes) +// { +// if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) +// { +// sticker = (TGDocumentAttributeSticker *)attribute; +// break; +// } +// } - TGPhotoMaskPosition *position = [self _positionForMask:sticker documentId:document.documentId]; + TGPhotoMaskPosition *position = nil; // [self _positionForMask:sticker documentId:document.documentId]; if (position != nil) { entity.position = position.center; diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintSettingsView.m b/submodules/LegacyComponents/Sources/TGPhotoPaintSettingsView.m index f8380bb730..b39238cf07 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintSettingsView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintSettingsView.m @@ -147,15 +147,15 @@ const CGFloat TGPhotoPaintSettingsPadPickerWidth = 360.0f; switch (icon) { case TGPhotoPaintSettingsViewIconBrush: - iconImage = TGComponentsImageNamed(@"PaintBrushIcon"); + iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Brush"], [UIColor whiteColor]); break; case TGPhotoPaintSettingsViewIconText: - iconImage = TGComponentsImageNamed(@"PaintTextSettingsIcon"); + iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Font"], [UIColor whiteColor]); break; case TGPhotoPaintSettingsViewIconMirror: - iconImage = TGComponentsImageNamed(@"PaintMirrorIcon"); + iconImage = TGTintedImage([UIImage imageNamed:@"Editor/Flip"], [UIColor whiteColor]); break; } diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintStickerEntity.m b/submodules/LegacyComponents/Sources/TGPhotoPaintStickerEntity.m index 68d7ab38a7..2b2e463413 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintStickerEntity.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintStickerEntity.m @@ -4,13 +4,16 @@ @implementation TGPhotoPaintStickerEntity -- (instancetype)initWithDocument:(TGDocumentMediaAttachment *)document baseSize:(CGSize)baseSize +@synthesize animated = _animated; + +- (instancetype)initWithDocument:(NSData *)document baseSize:(CGSize)baseSize animated:(bool)animated { self = [super init]; if (self != nil) { _document = document; _baseSize = baseSize; + _animated = animated; self.scale = 1.0; } return self; @@ -22,6 +25,7 @@ if (self != nil) { _emoji = emoji; + _animated = false; self.scale = 1.0f; } return self; @@ -31,7 +35,7 @@ { TGPhotoPaintStickerEntity *entity = nil; if (_document != nil) - entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:self.document baseSize:self.baseSize]; + entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:self.document baseSize:self.baseSize animated:self.animated]; else if (_emoji != nil) entity = [[TGPhotoPaintStickerEntity alloc] initWithEmoji:self.emoji]; else @@ -55,7 +59,7 @@ return false; TGPhotoPaintStickerEntity *entity = (TGPhotoPaintStickerEntity *)object; - return entity.uuid == self.uuid && [entity.document isEqual:self.document] && CGSizeEqualToSize(entity.baseSize, self.baseSize) && CGPointEqualToPoint(entity.position, self.position) && fabs(entity.scale - self.scale) < FLT_EPSILON && fabs(entity.angle - self.angle) < FLT_EPSILON && entity.mirrored == self.mirrored; + return entity.uuid == self.uuid && CGSizeEqualToSize(entity.baseSize, self.baseSize) && CGPointEqualToPoint(entity.position, self.position) && fabs(entity.scale - self.scale) < FLT_EPSILON && fabs(entity.angle - self.angle) < FLT_EPSILON && entity.mirrored == self.mirrored; } @end diff --git a/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.m b/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.m index ddc83ca5f9..7663338ae6 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.m +++ b/submodules/LegacyComponents/Sources/TGPhotoPaintTextEntity.m @@ -1,5 +1,8 @@ #import "TGPhotoPaintTextEntity.h" +#import "TGPhotoPaintFont.h" +#import "TGPaintSwatch.h" + @implementation TGPhotoPaintTextEntity - (instancetype)initWithText:(NSString *)text font:(TGPhotoPaintFont *)font swatch:(TGPaintSwatch *)swatch baseFontSize:(CGFloat)baseFontSize maxWidth:(CGFloat)maxWidth stroke:(bool)stroke @@ -30,6 +33,10 @@ return entity; } +- (bool)animated { + return false; +} + - (BOOL)isEqual:(id)object { if (object == self) diff --git a/submodules/LegacyComponents/Sources/TGPhotoQualityController.m b/submodules/LegacyComponents/Sources/TGPhotoQualityController.m index 5df162adfc..348146e4b1 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoQualityController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoQualityController.m @@ -664,7 +664,7 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f; { return [[[[[SSignal single:avAsset] delay:delay onQueue:[SQueue concurrentDefaultQueue]] mapToSignal:^SSignal *(AVAsset *avAsset) { - return [TGMediaVideoConverter convertAVAsset:avAsset adjustments:adjustments watcher:nil inhibitAudio:true]; + return [TGMediaVideoConverter convertAVAsset:avAsset adjustments:adjustments watcher:nil inhibitAudio:true entityRenderer:nil]; }] onError:^(__unused id error) { delay = 1.0; }] retryIf:^bool(__unused id error) diff --git a/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.h b/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.h index b29daf8353..6e8cc7cd00 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.h +++ b/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.h @@ -5,13 +5,14 @@ @end +@protocol TGPhotoPaintStickersContext; @interface TGPhotoStickerEntityView : TGPhotoPaintEntityView @property (nonatomic, readonly) TGPhotoPaintStickerEntity *entity; @property (nonatomic, readonly) bool isMirrored; -- (instancetype)initWithEntity:(TGPhotoPaintStickerEntity *)entity; +- (instancetype)initWithEntity:(TGPhotoPaintStickerEntity *)entity context:(id)context; - (void)mirror; - (UIImage *)image; diff --git a/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.m b/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.m index a3b62cac08..21635b65d1 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoStickerEntityView.m @@ -4,14 +4,13 @@ #import #import +#import #import "TGDocumentMediaAttachment.h" #import "TGStringUtils.h" #import "TGImageUtils.h" #import "TGColor.h" -#import "TGImageView.h" - const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; @interface UIView (OpaquePixel) @@ -33,9 +32,10 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; @interface TGPhotoStickerEntityView () { - TGImageView *_imageView; - - TGDocumentMediaAttachment *_document; + UIView *_stickerView; + + id _document; + bool _animated; bool _mirrored; CGSize _baseSize; @@ -45,7 +45,7 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; @implementation TGPhotoStickerEntityView -- (instancetype)initWithEntity:(TGPhotoPaintStickerEntity *)entity +- (instancetype)initWithEntity:(TGPhotoPaintStickerEntity *)entity context:(id)context { self = [super initWithFrame:CGRectMake(0.0f, 0.0f, entity.baseSize.width, entity.baseSize.height)]; if (self != nil) @@ -54,58 +54,24 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; _baseSize = entity.baseSize; _mirrored = entity.isMirrored; - _imageView = [[TGImageView alloc] init]; - _imageView.contentMode = UIViewContentModeScaleAspectFit; - _imageView.expectExtendedEdges = true; - [self addSubview:_imageView]; + _stickerView = [context stickerViewForDocument:entity.document]; + [self addSubview:_stickerView]; - TGDocumentMediaAttachment *sticker = entity.document; - _document = sticker; + _document = entity.document; + _animated = entity.animated; - CGSize imageSize = CGSizeZero; - bool isSticker = false; - for (id attribute in sticker.attributes) - { - if ([attribute isKindOfClass:[TGDocumentAttributeImageSize class]]) - imageSize = ((TGDocumentAttributeImageSize *)attribute).size; - else if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) - isSticker = true; - } + CGSize imageSize = CGSizeMake(512.0f, 512.0f); CGSize displaySize = [self fittedSizeForSize:imageSize maxSize:CGSizeMake(512.0f, 512.0f)]; - - NSMutableString *imageUri = [[NSMutableString alloc] init]; - [imageUri appendString:@"sticker://?"]; - if (sticker.documentId != 0) - { - [imageUri appendFormat:@"&documentId=%" PRId64, sticker.documentId]; - - TGMediaOriginInfo *originInfo = sticker.originInfo ?: [TGMediaOriginInfo mediaOriginInfoForDocumentAttachment:sticker]; - if (originInfo != nil) - [imageUri appendFormat:@"&origin_info=%@", [originInfo stringRepresentation]]; - } - else - { - [imageUri appendFormat:@"&localDocumentId=%" PRId64, sticker.localDocumentId]; - } - [imageUri appendFormat:@"&accessHash=%" PRId64, sticker.accessHash]; - [imageUri appendFormat:@"&datacenterId=%d", (int)sticker.datacenterId]; - [imageUri appendFormat:@"&fileName=%@", [TGStringUtils stringByEscapingForURL:sticker.fileName]]; - [imageUri appendFormat:@"&size=%d", (int)sticker.size]; - [imageUri appendFormat:@"&width=%d&height=%d", (int)displaySize.width, (int)displaySize.height]; - [imageUri appendFormat:@"&mime-type=%@", [TGStringUtils stringByEscapingForURL:sticker.mimeType]]; - [imageUri appendString:@"&inhibitBlur=1"]; - - _imageView.frame = CGRectMake(CGFloor((self.frame.size.width - displaySize.width) / 2.0f), CGFloor((self.frame.size.height - displaySize.height) / 2.0f), displaySize.width, displaySize.height); + + _stickerView.frame = CGRectMake(CGFloor((self.frame.size.width - displaySize.width) / 2.0f), CGFloor((self.frame.size.height - displaySize.height) / 2.0f), displaySize.width, displaySize.height); CGFloat scale = displaySize.width > displaySize.height ? self.frame.size.width / displaySize.width : self.frame.size.height / displaySize.height; _defaultTransform = CATransform3DMakeScale(scale, scale, 1.0f); - _imageView.layer.transform = _defaultTransform; + _stickerView.layer.transform = _defaultTransform; if (_mirrored) - _imageView.layer.transform = CATransform3DRotate(_defaultTransform, M_PI, 0, 1, 0); - - [_imageView loadUri:imageUri withOptions:@{}]; + _stickerView.layer.transform = CATransform3DRotate(_defaultTransform, M_PI, 0, 1, 0); } return self; } @@ -113,7 +79,7 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; - (TGPhotoPaintStickerEntity *)entity { - TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:_document baseSize:_baseSize]; + TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:_document baseSize:_baseSize animated:_animated]; entity.uuid = _entityUUID; entity.position = self.center; entity.scale = self.scale; @@ -154,11 +120,7 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; - (bool)precisePointInside:(CGPoint)point { - CGPoint imagePoint = [_imageView convertPoint:point fromView:self]; - if (![_imageView pointInside:[_imageView convertPoint:point fromView:self] withEvent:nil]) - return false; - - return [_imageView isOpaqueAtPoint:imagePoint]; + return [_stickerView pointInside:[_stickerView convertPoint:point fromView:self] withEvent:nil]; } - (void)mirror @@ -170,29 +132,29 @@ const CGFloat TGPhotoStickerSelectionViewHandleSide = 30.0f; CATransform3D startTransform = _defaultTransform; if (!_mirrored) { - startTransform = _imageView.layer.transform; + startTransform = _stickerView.layer.transform; } CATransform3D targetTransform = CATransform3DRotate(_defaultTransform, 0, 0, 1, 0); if (_mirrored) { targetTransform = CATransform3DRotate(_defaultTransform, M_PI, 0, 1, 0); - targetTransform.m34 = -1.0f / _imageView.frame.size.width; + targetTransform.m34 = -1.0f / _stickerView.frame.size.width; } [UIView animateWithDuration:0.25 animations:^ { - _imageView.layer.transform = targetTransform; + _stickerView.layer.transform = targetTransform; }]; } else { - _imageView.layer.transform = CATransform3DRotate(_defaultTransform, _mirrored ? M_PI : 0, 0, 1, 0); + _stickerView.layer.transform = CATransform3DRotate(_defaultTransform, _mirrored ? M_PI : 0, 0, 1, 0); } } - (UIImage *)image { - return _imageView.currentImage; + return [_stickerView image]; } - (TGPhotoPaintEntitySelectionView *)createSelectionView diff --git a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.h b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.h index a297ab44c0..678a6dbf8d 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.h +++ b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.h @@ -11,6 +11,7 @@ @interface TGPhotoTextEntityView : TGPhotoPaintEntityView @property (nonatomic, readonly) TGPhotoPaintTextEntity *entity; +@property (nonatomic, readonly) UIImage *image; @property (nonatomic, readonly) bool isEmpty; diff --git a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m index 057d9253f2..21417f2db9 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoTextEntityView.m @@ -1,5 +1,8 @@ #import "TGPhotoTextEntityView.h" +#import "TGPaintSwatch.h" +#import "TGPhotoPaintFont.h" + #import "TGColor.h" #import "LegacyComponentsInternal.h" @@ -102,6 +105,20 @@ const CGFloat TGPhotoTextSelectionViewHandleSide = 30.0f; return entity; } +- (UIImage *)image { + CGRect rect = self.bounds; + + UIGraphicsBeginImageContextWithOptions(CGSizeMake(rect.size.width, rect.size.height), false, 1.0f); + CGContextRef context = UIGraphicsGetCurrentContext(); + + [self drawViewHierarchyInRect:rect afterScreenUpdates:false]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + - (bool)isEditing { return _textView.isFirstResponder; diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m index 2d15f274b7..274776bdf2 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoToolbarView.m @@ -2,6 +2,7 @@ #import "LegacyComponentsInternal.h" #import "TGFont.h" +#import "TGImageUtils.h" #import "TGModernButton.h" #import "TGPhotoEditorButton.h" @@ -49,7 +50,7 @@ switch (backButton) { case TGPhotoEditorBackButtonCancel: - cancelImage = TGComponentsImageNamed(@"PhotoPickerCancelIcon"); + cancelImage = TGTintedImage([UIImage imageNamed:@"Editor/Cancel"], [UIColor whiteColor]); break; default: @@ -65,7 +66,7 @@ switch (doneButton) { case TGPhotoEditorDoneButtonCheck: - doneImage = TGComponentsImageNamed(@"PhotoPickerDoneIcon"); + doneImage = TGTintedImage([UIImage imageNamed:@"Editor/Commit"], [UIColor whiteColor]); break; default: diff --git a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m index 62cb623234..d787723785 100644 --- a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m +++ b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m @@ -4,6 +4,9 @@ #import "TGPaintingData.h" +#import "TGPhotoPaintStickerEntity.h" +#import "TGPhotoPaintTextEntity.h" + const NSTimeInterval TGVideoEditMinimumTrimmableDuration = 1.0; const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; @@ -68,12 +71,50 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; } if (dictionary[@"originalSize"]) adjustments->_originalSize = [dictionary[@"originalSize"] CGSizeValue]; - if (dictionary[@"paintingImagePath"]) + if (dictionary[@"entities"]) { + NSMutableArray *entities = [[NSMutableArray alloc] init]; + + for (NSDictionary *dict in dictionary[@"entities"]) { + if ([dict[@"type"] isEqualToString:@"sticker"]) { + TGPhotoPaintStickerEntity *entity = [[TGPhotoPaintStickerEntity alloc] initWithDocument:dict[@"data"] baseSize:[dict[@"baseSize"] CGSizeValue] animated:[dict[@"animated"] boolValue]]; + entity.uuid = [dict[@"uuid"] integerValue]; + entity.position = [dict[@"position"] CGPointValue]; + entity.scale = [dict[@"scale"] floatValue]; + entity.angle = [dict[@"angle"] floatValue]; + entity.mirrored = [dict[@"mirrored"] boolValue]; + [entities addObject:entity]; + } else if ([dict[@"type"] isEqualToString:@"text"]) { + UIImage *renderImage = [[UIImage alloc] initWithData:dict[@"data"]]; + if (renderImage != nil) { + TGPhotoPaintTextEntity *entity = [[TGPhotoPaintTextEntity alloc] initWithText:nil font:nil swatch:nil baseFontSize:0.0 maxWidth:0.0 stroke:false]; + entity.uuid = [dict[@"uuid"] integerValue]; + entity.position = [dict[@"position"] CGPointValue]; + entity.scale = [dict[@"scale"] floatValue]; + entity.angle = [dict[@"angle"] floatValue]; + entity.renderImage = renderImage; + [entities addObject:entity]; + } + } + } + + adjustments->_paintingData = [TGPaintingData dataWithPaintingImagePath:dictionary[@"paintingImagePath"] entities:entities]; + } else if (dictionary[@"paintingImagePath"]) { adjustments->_paintingData = [TGPaintingData dataWithPaintingImagePath:dictionary[@"paintingImagePath"]]; + } if (dictionary[@"sendAsGif"]) adjustments->_sendAsGif = [dictionary[@"sendAsGif"] boolValue]; if (dictionary[@"preset"]) adjustments->_preset = (TGMediaVideoConversionPreset)[dictionary[@"preset"] integerValue]; + if (dictionary[@"tools"]) { + NSMutableDictionary *tools = [[NSMutableDictionary alloc] init]; + for (NSString *key in dictionary[@"tools"]) { + id value = dictionary[@"tools"][key]; + if ([value isKindOfClass:[NSNumber class]]) { + tools[key] = value; + } + } + adjustments->_toolValues = tools; + } return adjustments; } @@ -102,6 +143,7 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; adjustments->_paintingData = _paintingData; adjustments->_sendAsGif = _sendAsGif; adjustments->_preset = preset; + adjustments->_toolValues = _toolValues; if (maxDuration > DBL_EPSILON) { @@ -135,8 +177,59 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; dict[@"originalSize"] = [NSValue valueWithCGSize:self.originalSize]; - if (self.paintingData.imagePath != nil) - dict[@"paintingImagePath"] = self.paintingData.imagePath; + if (self.toolValues.count > 0) { + NSMutableDictionary *tools = [[NSMutableDictionary alloc] init]; + for (NSString *key in self.toolValues) { + id value = self.toolValues[key]; + if ([value isKindOfClass:[NSNumber class]]) { + tools[key] = value; + } + } + dict[@"tools"] = tools; + } + + if (self.paintingData != nil) { + if (self.paintingData.imagePath != nil) { + dict[@"paintingImagePath"] = self.paintingData.imagePath; + } + + NSMutableArray *entities = [[NSMutableArray alloc] init]; + + if (self.paintingData.entities != nil) { + for (TGPhotoPaintEntity *entity in self.paintingData.entities) { + if ([entity isKindOfClass:[TGPhotoPaintStickerEntity class]]) { + TGPhotoPaintStickerEntity *stickerEntity = (TGPhotoPaintStickerEntity *)entity; + NSMutableDictionary *sticker = [[NSMutableDictionary alloc] init]; + sticker[@"type"] = @"sticker"; + sticker[@"baseSize"] = [NSValue valueWithCGSize:stickerEntity.baseSize]; + sticker[@"uuid"] = @(stickerEntity.uuid); + sticker[@"data"] = stickerEntity.document; + sticker[@"position"] = [NSValue valueWithCGPoint:stickerEntity.position]; + sticker[@"scale"] = @(stickerEntity.scale); + sticker[@"angle"] = @(stickerEntity.angle); + sticker[@"mirrored"] = @(stickerEntity.mirrored); + sticker[@"animated"] = @(stickerEntity.animated); + [entities addObject:sticker]; + } else if ([entity isKindOfClass:[TGPhotoPaintTextEntity class]]) { + TGPhotoPaintTextEntity *textEntity = (TGPhotoPaintTextEntity *)entity; + NSMutableDictionary *text = [[NSMutableDictionary alloc] init]; + if (textEntity.renderImage != nil) { + text[@"type"] = @"text"; + text[@"uuid"] = @(textEntity.uuid); + text[@"data"] = UIImagePNGRepresentation(textEntity.renderImage); + text[@"position"] = [NSValue valueWithCGPoint:textEntity.position]; + text[@"scale"] = @(textEntity.scale); + text[@"angle"] = @(textEntity.angle); + [entities addObject:text]; + } + } + } + } + + if (entities.count > 0) { + dict[@"entities"] = entities; + } + } dict[@"sendAsGif"] = @(self.sendAsGif); diff --git a/submodules/LegacyMediaPickerUI/BUILD b/submodules/LegacyMediaPickerUI/BUILD index 4a258f2a6e..1ae531d0cf 100644 --- a/submodules/LegacyMediaPickerUI/BUILD +++ b/submodules/LegacyMediaPickerUI/BUILD @@ -22,6 +22,9 @@ swift_library( "//submodules/SearchPeerMembers:SearchPeerMembers", "//submodules/SaveToCameraRoll:SaveToCameraRoll", "//submodules/LegacyMediaPickerUI/LegacyImageProcessors:LegacyImageProcessors", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/StickerResources:StickerResources", ], visibility = [ "//visibility:public", diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index 6cb8d27bfe..d267bba071 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -59,7 +59,7 @@ public enum LegacyAttachmentMenuMediaEditing { case file } -public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { +public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, presentationData: PresentationData, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentSchedulePicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> Void, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { let defaultVideoPreset = defaultVideoPresetForContext(context) UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0") @@ -106,9 +106,19 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaO selectionLimit = 10 } + let paintStickersContext = LegacyPaintStickersContext(context: context) + paintStickersContext.presentStickersController = { completion in + presentStickers({ file, animated, view, rect in + let coder = PostboxEncoder() + coder.encodeRootObject(file) + completion?(coder.makeData(), animated, view, rect) + }) + } + if canSendImageOrVideo { let carouselItem = TGAttachmentCarouselItemView(context: parentController.context, camera: PGCamera.cameraAvailable(), selfPortrait: false, forProfilePhoto: false, assetType: TGMediaAssetAnyType, saveEditedPhotos: !isSecretChat && saveEditedPhotos, allowGrouping: editMediaOptions == nil && allowGrouping, allowSelection: editMediaOptions == nil, allowEditing: true, document: false, selectionLimit: selectionLimit)! carouselItemView = carouselItem + carouselItem.stickersContext = paintStickersContext carouselItem.suggestionContext = legacySuggestionContext(context: context, peerId: peer.id) carouselItem.recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) carouselItem.cameraPressed = { [weak controller, weak parentController] cameraView in diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickerView.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickerView.swift new file mode 100644 index 0000000000..60b4f7193f --- /dev/null +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickerView.swift @@ -0,0 +1,118 @@ +import UIKit +import Display +import SyncCore +import TelegramCore +import AccountContext +import SwiftSignalKit +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import StickerResources +import LegacyComponents + +class LegacyPaintStickerView: UIView, TGPhotoPaintStickerRenderView { + private let context: AccountContext + private let file: TelegramMediaFile + private var currentSize: CGSize? + private var dimensions: CGSize? + + private let imageNode: TransformImageNode + private var animationNode: AnimatedStickerNode? + + private var didSetUpAnimationNode = false + private let stickerFetchedDisposable = MetaDisposable() + + init(context: AccountContext, file: TelegramMediaFile) { + self.context = context + self.file = file + + self.imageNode = TransformImageNode() + + super.init(frame: CGRect()) + + self.addSubnode(self.imageNode) + + self.setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.stickerFetchedDisposable.dispose() + } + + func image() -> UIImage! { + if self.imageNode.contents != nil { + return UIImage(cgImage: self.imageNode.contents as! CGImage) + } else { + return nil + } + } + + private func setup() { + if let dimensions = self.file.dimensions { + if self.file.isAnimatedSticker { + if self.animationNode == nil { + let animationNode = AnimatedStickerNode() + self.animationNode = animationNode + animationNode.started = { [weak self] in + self?.imageNode.isHidden = true + } + self.addSubnode(animationNode) + } + let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) + self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: self.context.account.postbox, file: self.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)))) + self.updateVisibility() + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, fileReference: stickerPackFileReference(self.file), resource: self.file.resource).start()) + } else { + if let animationNode = self.animationNode { + animationNode.visibility = false + self.animationNode = nil + animationNode.removeFromSupernode() + self.imageNode.isHidden = false + self.didSetUpAnimationNode = false + } + self.imageNode.setSignal(chatMessageSticker(account: self.context.account, file: self.file, small: false, synchronousLoad: false)) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: self.context.account, fileReference: stickerPackFileReference(self.file), resource: chatMessageStickerResource(file: self.file, small: false)).start()) + } + + self.dimensions = dimensions.cgSize + self.setNeedsLayout() + } + } + + func updateVisibility() { + if !self.didSetUpAnimationNode { + self.didSetUpAnimationNode = true + + self.animationNode?.visibility = true + let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) + let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)) + self.animationNode?.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + let size = self.bounds.size + + if size.width > 0 && self.currentSize != size { + self.currentSize = size + + let sideSize: CGFloat = size.width + let boundingSize = CGSize(width: sideSize, height: sideSize) + + if let dimensions = self.dimensions { + let imageSize = dimensions.aspectFitted(boundingSize) + self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() + self.imageNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) + if let animationNode = self.animationNode { + animationNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: (size.height - imageSize.height) / 2.0), size: imageSize) + animationNode.updateLayout(size: imageSize) + } + } + } + } +} diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift new file mode 100644 index 0000000000..78df4321f2 --- /dev/null +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -0,0 +1,316 @@ +import LegacyComponents +import Display +import Postbox +import SwiftSignalKit +import SyncCore +import TelegramCore +import AccountContext +import AnimatedStickerNode +import TelegramAnimatedStickerNode +import YuvConversion +import StickerResources + +protocol LegacyPaintEntity { + var position: CGPoint { get } + var scale: CGFloat { get } + var angle: CGFloat { get } + var baseSize: CGSize? { get } + + func image(for time: CMTime, completion: @escaping (CIImage?) -> Void) +} + +private class LegacyPaintStickerEntity: LegacyPaintEntity { + var position: CGPoint { + return self.entity.position + } + + var scale: CGFloat { + return self.entity.scale + } + + var angle: CGFloat { + return self.entity.angle + } + + var baseSize: CGSize? { + return self.entity.baseSize + } + + let account: Account + let file: TelegramMediaFile + let entity: TGPhotoPaintStickerEntity + let animated: Bool + + var source: AnimatedStickerNodeSource? + var frameSource: AnimatedStickerFrameSource? + var frameQueue: QueueLocalObject? + + let queue = Queue() + let disposable = MetaDisposable() + + let imagePromise = Promise() + + init?(account: Account, entity: TGPhotoPaintStickerEntity) { + let decoder = PostboxDecoder(buffer: MemoryBuffer(data: entity.document)) + if let file = decoder.decodeRootObject() as? TelegramMediaFile { + self.account = account + self.entity = entity + self.file = file + self.animated = file.isAnimatedSticker + + if file.isAnimatedSticker { + self.source = AnimatedStickerResourceSource(account: account, resource: file.resource) + if let source = self.source { + let dimensions = self.file.dimensions ?? PixelDimensions(width: 512, height: 512) + let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 256.0, height: 256.0)) + self.disposable.set((source.cachedDataPath(width: Int(fittedDimensions.width), height: Int(fittedDimensions.height)) + |> deliverOn(self.queue)).start(next: { [weak self] path, complete in + if let strongSelf = self, complete { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + let queue = strongSelf.queue + let frameSource = AnimatedStickerCachedFrameSource(queue: queue, data: data, complete: complete, notifyUpdated: {})! + + let frameQueue = QueueLocalObject(queue: queue, generate: { + return AnimatedStickerFrameQueue(queue: queue, length: 1, source: frameSource) + }) + + strongSelf.frameQueue = frameQueue + strongSelf.frameSource = frameSource + } + } + })) + } + } else { + self.disposable.set((chatMessageSticker(account: self.account, file: self.file, small: false, fetched: true, onlyFullSize: true, thumbnail: false, synchronousLoad: false) + |> deliverOn(self.queue)).start(next: { [weak self] generator in + if let strongSelf = self { + let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: entity.baseSize, boundingSize: entity.baseSize, intrinsicInsets: UIEdgeInsets())) + let image = context?.generateImage() + if let image = image { + strongSelf.imagePromise.set(.single(image)) + } + } + })) + } + } else { + return nil + } + } + + deinit { + self.disposable.dispose() + } + + private func render(width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType) -> CIImage? { + let calculatedBytesPerRow = (4 * Int(width) + 15) & (~15) + assert(bytesPerRow == calculatedBytesPerRow) + + let image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData, bytesPerRow in + switch type { + case .yuva: + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + decodeYUVAToRGBA(bytes, pixelData, Int32(width), Int32(height), Int32(bytesPerRow)) + } + case .argb: + data.withUnsafeBytes { (bytes: UnsafePointer) -> Void in + memcpy(pixelData, bytes, data.count) + } + } + }) + + if let image = image { + return CIImage(image: image) + } else { + return nil + } + } + + var cachedCIImage: CIImage? + func image(for time: CMTime, completion: @escaping (CIImage?) -> Void) { + if self.animated { + let frameQueue = self.frameQueue + self.queue.async { + guard let frameQueue = frameQueue else { + completion(nil) + return + } + let maybeFrame = frameQueue.syncWith { frameQueue in + return frameQueue.take() + } + if let maybeFrame = maybeFrame, let frame = maybeFrame { + let image = self.render(width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type) + completion(image) + } else { + completion(nil) + } + frameQueue.with { frameQueue in + frameQueue.generateFramesIfNeeded() + } + } + } else { + self.queue.async { + var image: CIImage? + if let cachedImage = self.cachedCIImage { + image = cachedImage + completion(image) + } else { + let _ = (self.imagePromise.get() + |> deliverOn(self.queue)).start(next: { [weak self] image in + if let strongSelf = self { + strongSelf.cachedCIImage = CIImage(image: image) + completion(strongSelf.cachedCIImage) + } + }) + } + } + } + } +} + +private class LegacyPaintTextEntity: LegacyPaintEntity { + var position: CGPoint { + return self.entity.position + } + + var scale: CGFloat { + return self.entity.scale + } + + var angle: CGFloat { + return self.entity.angle + } + + var baseSize: CGSize? { + return nil + } + + let entity: TGPhotoPaintTextEntity + + init(entity: TGPhotoPaintTextEntity) { + self.entity = entity + } + + var cachedCIImage: CIImage? + func image(for time: CMTime, completion: @escaping (CIImage?) -> Void) { + var image: CIImage? + if let cachedImage = self.cachedCIImage { + image = cachedImage + } else if let renderImage = entity.renderImage { + image = CIImage(image: renderImage) + self.cachedCIImage = image + } + completion(image) + } +} + +public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRenderer { + private let account: Account + private let queue = Queue() + + private let entities: [LegacyPaintEntity] + private let originalSize: CGSize + private let cropRect: CGRect? + + public init(account: Account, adjustments: TGMediaEditAdjustments) { + self.account = account + self.originalSize = adjustments.originalSize + self.cropRect = adjustments.cropRect.isEmpty ? nil : adjustments.cropRect + + var entities: [LegacyPaintEntity] = [] + if let paintingData = adjustments.paintingData, let paintingEntities = paintingData.entities { + for paintingEntity in paintingEntities { + if let sticker = paintingEntity as? TGPhotoPaintStickerEntity { + if let entity = LegacyPaintStickerEntity(account: account, entity: sticker) { + entities.append(entity) + } + } else if let text = paintingEntity as? TGPhotoPaintTextEntity { + entities.append(LegacyPaintTextEntity(entity: text)) + } + } + } + self.entities = entities + + super.init() + } + + deinit { + + } + + public func entities(for time: CMTime, size: CGSize, completion: (([CIImage]?) -> Void)!) { + let entities = self.entities +// let originalSize = self.originalSize +// let cropRect = self.cropRect + + self.queue.async { + if entities.isEmpty { + completion(nil) + } else { + let count = Atomic(value: 1) + let images = Atomic<[CIImage]>(value: []) + let maybeFinalize = { + let count = count.modify { current -> Int in + return current - 1 + } + if count == 0 { + completion(images.with { $0 }) + } + } + for entity in entities { + let _ = count.modify { current -> Int in + return current + 1 + } + entity.image(for: time, completion: { image in + if var image = image { + let paintingScale = max(size.width, size.height) / 1920.0 + + var transform = CGAffineTransform(translationX: -image.extent.midX, y: -image.extent.midY) + image = image.transformed(by: transform) + + var scale = entity.scale * paintingScale + if let baseSize = entity.baseSize { + scale *= baseSize.width / image.extent.size.width + } + + transform = CGAffineTransform(translationX: entity.position.x * paintingScale, y: size.height - entity.position.y * paintingScale) + transform = transform.rotated(by: CGFloat.pi * 2 - entity.angle) + transform = transform.scaledBy(x: scale, y: scale) + + image = image.transformed(by: transform) + let _ = images.modify { current in + var updated = current + updated.append(image) + return updated + } + } + maybeFinalize() + }) + } + maybeFinalize() + } + } + } +} + +public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersContext { + public var presentStickersController: ((((Any?, Bool, UIView?, CGRect) -> Void)?) -> Void)! + + private let context: AccountContext + + public init(context: AccountContext) { + self.context = context + } + + public func stickerView(forDocument document: Any!) -> (UIView & TGPhotoPaintStickerRenderView)! { + if let data = document as? Data{ + let decoder = PostboxDecoder(buffer: MemoryBuffer(data: data)) + if let file = decoder.decodeRootObject() as? TelegramMediaFile { + return LegacyPaintStickerView(context: self.context, file: file) + } else { + return nil + } + } else { + return nil + } + } +} diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift index 47581bc752..70501d7344 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift @@ -203,37 +203,37 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! ThemeAutoNightSettingsControllerArguments switch self { - case let .modeSystem(theme, title, value): + case let .modeSystem(_, title, value): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.system) }) - case let .modeDisabled(theme, title, value): + case let .modeDisabled(_, title, value): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.none) }) - case let .modeTimeBased(theme, title, value): + case let .modeTimeBased(_, title, value): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.timeBased) }) - case let .modeBrightness(theme, title, value): + case let .modeBrightness(_, title, value): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { arguments.updateMode(.brightness) }) - case let .settingsHeader(theme, title): + case let .settingsHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) - case let .timeBasedAutomaticLocation(theme, title, value): + case let .timeBasedAutomaticLocation(_, title, value): return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in arguments.updateTimeBasedAutomatic(value) }) - case let .timeBasedAutomaticLocationValue(theme, title, value): + case let .timeBasedAutomaticLocationValue(_, title, value): return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, titleColor: .accent, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: { arguments.updateTimeBasedAutomaticLocation() }) - case let .timeBasedManualFrom(theme, title, value): + case let .timeBasedManualFrom(_, title, value): return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openTimeBasedManual(.from) }) - case let .timeBasedManualTo(theme, title, value): + case let .timeBasedManualTo(_, title, value): return ItemListDisclosureItem(presentationData: presentationData, icon: nil, title: title, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: { arguments.openTimeBasedManual(.to) }) @@ -241,9 +241,9 @@ private enum ThemeAutoNightSettingsControllerEntry: ItemListNodeEntry { return ThemeSettingsBrightnessItem(theme: theme, value: Int32(value * 100.0), sectionId: self.section, updated: { value in arguments.updateAutomaticBrightness(Double(value) / 100.0) }) - case let .settingInfo(theme, text): + case let .settingInfo(_, text): return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) - case let .themeHeader(theme, title): + case let .themeHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .themeItem(theme, strings, themes, allThemes, currentTheme, themeSpecificAccentColors, themeSpecificChatWallpapers): return ThemeSettingsThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: themes, allThemes: allThemes, displayUnsupported: false, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, currentTheme: currentTheme, updatedTheme: { theme in diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index 0c0215dbb1..58b62d7b59 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -225,7 +225,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation public func descriptionStringForMessage(contentSettings: ContentSettings, message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) { if !message.text.isEmpty { - return (message.text, false) + return (foldLineBreaks(message.text), false) } return stringForMediaKind(messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId), strings: strings) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index fbcb3dd245..dfbaca2704 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5880,7 +5880,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } strongSelf.chatDisplayNode.dismissInput() - + var bannedSendMedia: (Int32, Bool)? var canSendPolls = true if let channel = peer as? TelegramChannel { @@ -6064,6 +6064,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) }) } + }, presentStickers: { [weak self] completion in + if let strongSelf = self { + let controller = DrawingStickersScreen(context: strongSelf.context, selectSticker: { fileReference, node, rect in + completion(fileReference.media, fileReference.media.isAnimatedSticker, node.view, rect) + return true + }) + strongSelf.present(controller, in: .window(.root)) + } }, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) }) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 6cf9c17d20..55bd83627e 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -18,24 +18,24 @@ import GalleryUI import OverlayStatusController import PresentationDataUtils -private struct PeerSpecificPackData { +struct PeerSpecificPackData { let peer: Peer let info: StickerPackCollectionInfo let items: [ItemCollectionItem] } -private enum CanInstallPeerSpecificPack { +enum CanInstallPeerSpecificPack { case none case available(peer: Peer, dismissed: Bool) } -private struct ChatMediaInputPanelTransition { +struct ChatMediaInputPanelTransition { let deletions: [ListViewDeleteItem] let insertions: [ListViewInsertItem] let updates: [ListViewUpdateItem] } -private struct ChatMediaInputGridTransition { +struct ChatMediaInputGridTransition { let deletions: [Int] let insertions: [GridNodeInsertItem] let updates: [GridNodeUpdateItem] @@ -46,7 +46,7 @@ private struct ChatMediaInputGridTransition { let animated: Bool } -private func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fromEntries: [ChatMediaInputPanelEntry], to toEntries: [ChatMediaInputPanelEntry], inputNodeInteraction: ChatMediaInputNodeInteraction) -> ChatMediaInputPanelTransition { +func preparedChatMediaInputPanelEntryTransition(context: AccountContext, from fromEntries: [ChatMediaInputPanelEntry], to toEntries: [ChatMediaInputPanelEntry], inputNodeInteraction: ChatMediaInputNodeInteraction) -> ChatMediaInputPanelTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -56,7 +56,7 @@ private func preparedChatMediaInputPanelEntryTransition(context: AccountContext, return ChatMediaInputPanelTransition(deletions: deletions, insertions: insertions, updates: updates) } -private func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition { +func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemCollectionsView, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingInteraction: TrendingPaneInteraction) -> ChatMediaInputGridTransition { var stationaryItems: GridNodeStationaryItems = .none var scrollToItem: GridNodeScrollToItem? var animated = false @@ -163,9 +163,11 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, view: I return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated) } -private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasUnreadTrending: Bool?, theme: PresentationTheme) -> [ChatMediaInputPanelEntry] { +func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasGifs: Bool = true, hasUnreadTrending: Bool?, theme: PresentationTheme) -> [ChatMediaInputPanelEntry] { var entries: [ChatMediaInputPanelEntry] = [] - entries.append(.recentGifs(theme)) + if hasGifs { + entries.append(.recentGifs(theme)) + } if let hasUnreadTrending = hasUnreadTrending { entries.append(.trending(hasUnreadTrending, theme)) } @@ -215,7 +217,7 @@ private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers return entries } -private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] { +func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] { var entries: [ChatMediaInputGridEntry] = [] if view.lower == nil { @@ -297,7 +299,7 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: return entries } -private enum StickerPacksCollectionPosition: Equatable { +enum StickerPacksCollectionPosition: Equatable { case initial case scroll(aroundIndex: ItemCollectionViewEntryIndex?) case navigate(index: ItemCollectionViewEntryIndex?, collectionId: ItemCollectionId?) @@ -322,7 +324,7 @@ private enum StickerPacksCollectionPosition: Equatable { } } -private enum StickerPacksCollectionUpdate { +enum StickerPacksCollectionUpdate { case initial case generic case scroll @@ -353,7 +355,7 @@ final class ChatMediaInputNodeInteraction { } } -private func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> StickerPacksCollectionPosition { +func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> StickerPacksCollectionPosition { switch position { case let .scroll(index): if let index = index, index.collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue || index.collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue { @@ -365,13 +367,12 @@ private func clipScrollPosition(_ position: StickerPacksCollectionPosition) -> S return position } -private enum ChatMediaInputPaneType { +enum ChatMediaInputPaneType { case gifs case stickers - //case trending } -private struct ChatMediaInputPaneArrangement { +struct ChatMediaInputPaneArrangement { let panes: [ChatMediaInputPaneType] let currentIndex: Int let indexTransition: CGFloat @@ -385,7 +386,7 @@ private struct ChatMediaInputPaneArrangement { } } -private final class CollectionListContainerNode: ASDisplayNode { +final class CollectionListContainerNode: ASDisplayNode { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for subview in self.view.subviews { if let result = subview.hitTest(point.offsetBy(dx: -subview.frame.minX, dy: -subview.frame.minY), with: event) { diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift new file mode 100644 index 0000000000..2be1d2579c --- /dev/null +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -0,0 +1,901 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import TelegramUIPreferences +import MergeLists +import StickerPackPreviewUI +import OverlayStatusController +import PresentationDataUtils +import SearchBarNode +import UndoUI +import SegmentedControlNode + +private final class DrawingStickersScreenNode: ViewControllerTracingNode { + private let context: AccountContext + private var presentationData: PresentationData + private let selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? + private var searchItemContext = StickerPaneSearchGlobalItemContext() + private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)> + + private let controllerInteraction: ChatControllerInteraction + private var inputNodeInteraction: ChatMediaInputNodeInteraction! + + private let collectionListPanel: ASDisplayNode + private let collectionListContainer: CollectionListContainerNode + + private let blurView: UIView + + private let segmentedControlNode: SegmentedControlNode + private let cancelButton: HighlightableButtonNode + + private let listView: ListView + + private var searchContainerNode: PaneSearchContainerNode? + private let searchContainerNodeLoadedDisposable = MetaDisposable() + + private let stickerPane: ChatMediaInputStickerPane + + private let itemCollectionsViewPosition = Promise() + private var currentStickerPacksCollectionPosition: StickerPacksCollectionPosition? + private var currentView: ItemCollectionsView? + + private var paneArrangement: ChatMediaInputPaneArrangement + private var initializedArrangement = false + + private var validLayout: ContainerViewLayout? + + private var disposable = MetaDisposable() + + private let _ready = Promise() + var ready: Promise { + return self._ready + } + private var didSetReady: Bool = false + + fileprivate var dismiss: (() -> Void)? + + init(context: AccountContext, selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) { + self.context = context + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.selectSticker = selectSticker + + self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings)) + + var selectStickerImpl: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? + + self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in + return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, presentController: { _, _ in }, navigationController: { + return nil + }, chatControllerNode: { + return nil + }, reactionContainerNode: { + return nil + }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in + }, canSetupReply: { _ in + return .none + }, navigateToFirstDateMessage: { _ in + }, requestRedeliveryOfFailedMessages: { _ in + }, addContact: { _ in + }, rateCall: { _, _ in + }, requestSelectMessagePollOptions: { _, _ in + }, requestOpenMessagePollResults: { _, _ in + }, openAppStorePage: { + }, displayMessageTooltip: { _, _, _, _ in + }, seekToTimecode: { _, _, _ in + }, scheduleCurrentMessage: { + }, sendScheduledMessagesNow: { _ in + }, editScheduledMessagesTime: { _ in + }, performTextSelectionAction: { _, _, _ in + }, updateMessageLike: { _, _ in + }, openMessageReactions: { _ in + }, displaySwipeToReplyHint: { + }, dismissReplyMarkupMessage: { _ in + }, openMessagePollResults: { _, _ in + }, openPollCreation: { _ in + }, displayPollSolution: { _, _ in + }, displayPsa: { _, _ in + }, displayDiceTooltip: { _ in + }, animateDiceSuccess: { + }, requestMessageUpdate: { _ in + }, cancelInteractiveKeyboardGestures: { + }, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, + pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: true)) + + self.blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark)) + + let segmentedTheme = SegmentedControlTheme(backgroundColor: UIColor(rgb: 0x2c2d2d), foregroundColor: UIColor(rgb: 0x656565), shadowColor: UIColor.clear, textColor: .white, dividerColor: .white) + + self.segmentedControlNode = SegmentedControlNode(theme: segmentedTheme, items: [SegmentedControlItem(title: "Stickers"), SegmentedControlItem(title: "Masks")], selectedIndex: 0) + + self.cancelButton = HighlightableButtonNode() + self.cancelButton.setAttributedTitle(NSAttributedString(string: self.presentationData.strings.Common_Cancel, font: Font.regular(17.0), textColor: .white), for: .normal) + + self.collectionListPanel = ASDisplayNode() + self.collectionListPanel.clipsToBounds = true + + self.collectionListPanel.backgroundColor = UIColor(rgb: 0x151515) + + self.collectionListContainer = CollectionListContainerNode() + self.collectionListContainer.clipsToBounds = true + + self.listView = ListView() + self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0) + + var paneDidScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void)? + self.stickerPane = ChatMediaInputStickerPane(theme: self.presentationData.theme, strings: self.presentationData.strings, paneDidScroll: { pane, state, transition in + paneDidScrollImpl?(pane, state, transition) + }, fixPaneScroll: { pane, state in + // fixPaneScrollImpl?(pane, state) + }) + + self.paneArrangement = ChatMediaInputPaneArrangement(panes: [.stickers], currentIndex: 0, indexTransition: 0.0) + + super.init() + + self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) + + self.view.addSubview(self.blurView) + + self.inputNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in + if let strongSelf = self, let currentView = strongSelf.currentView, (collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId || true) { + var index: Int32 = 0 + if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue { + strongSelf.setCurrentPane(.gifs, transition: .animated(duration: 0.25, curve: .spring)) + } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue { + strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen( + context: strongSelf.context, + sendSticker: { + fileReference, sourceNode, sourceRect in + if let strongSelf = self { + return strongSelf.controllerInteraction.sendSticker(fileReference, false, sourceNode, sourceRect) + } else { + return false + } + } + )) + } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue { + strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace) + strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) + } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue { + strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace) + strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) + } else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue { + strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring)) + strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId))) + } else { + strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring)) + for (id, _, _) in currentView.collectionInfos { + if id.namespace == collectionId.namespace { + if id == collectionId { + let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id) + strongSelf.currentStickerPacksCollectionPosition = .navigate(index: itemIndex, collectionId: nil) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: itemIndex, collectionId: nil))) + break + } + index += 1 + } + } + } + } + }, openSettings: { [weak self] in + if let strongSelf = self { +// let controller = installedStickerPacksController(context: context, mode: .modal) +// controller.navigationPresentation = .modal +// strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) + } + }, toggleSearch: { [weak self] value, searchMode in + if let strongSelf = self { + if let searchMode = searchMode, value { + var searchContainerNode: PaneSearchContainerNode? + if let current = strongSelf.searchContainerNode { + searchContainerNode = current + } else { + searchContainerNode = PaneSearchContainerNode(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction, inputNodeInteraction: strongSelf.inputNodeInteraction, mode: searchMode, trendingGifsPromise: Promise(nil), cancel: { + self?.searchContainerNode?.deactivate() + self?.inputNodeInteraction.toggleSearch(false, nil) + }) + strongSelf.searchContainerNode = searchContainerNode + } + if let searchContainerNode = searchContainerNode { + strongSelf.searchContainerNodeLoadedDisposable.set((searchContainerNode.ready + |> deliverOnMainQueue).start(next: { + if let strongSelf = self { + strongSelf.controllerInteraction.updateInputMode { current in + switch current { + case let .media(mode, _): + return .media(mode: mode, expanded: .search(searchMode)) + default: + return current + } + } + } + })) + } + } else { + strongSelf.controllerInteraction.updateInputMode { current in + switch current { + case let .media(mode, _): + return .media(mode: mode, expanded: nil) + default: + return current + } + } + } + } + }, openPeerSpecificSettings: { [weak self] in + }, dismissPeerSpecificSettings: { [weak self] in + }, clearRecentlyUsedStickers: { [weak self] in + if let strongSelf = self { + let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: strongSelf.presentationData.theme, fontSize: strongSelf.presentationData.listsFontSize)) + var items: [ActionSheetItem] = [] + items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + let _ = (context.account.postbox.transaction { transaction in + clearRecentlyUsedStickers(transaction: transaction) + }).start() + })) + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ])]) + strongSelf.controllerInteraction.presentController(actionSheet, nil) + } + }) + self.inputNodeInteraction.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: true) + + + self.collectionListPanel.addSubnode(self.listView) + self.collectionListContainer.addSubnode(self.collectionListPanel) + self.addSubnode(self.collectionListContainer) + + self.addSubnode(self.segmentedControlNode) + self.addSubnode(self.cancelButton) + + let trendingInteraction = TrendingPaneInteraction(installPack: { [weak self] info in + }, openPack: { [weak self] info in + }, getItemIsPreviewed: { item in + return false + }, openSearch: { + }) + + let itemCollectionsView = self.itemCollectionsViewPosition.get() + |> distinctUntilChanged + |> mapToSignal { position -> Signal<(ItemCollectionsView, StickerPacksCollectionUpdate), NoError> in + switch position { + case .initial: + var firstTime = true + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 50) + |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in + let update: StickerPacksCollectionUpdate + if firstTime { + firstTime = false + update = .initial + } else { + update = .generic + } + return (view, update) + } + case let .scroll(aroundIndex): + var firstTime = true + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: aroundIndex, count: 300) + |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in + let update: StickerPacksCollectionUpdate + if firstTime { + firstTime = false + update = .scroll + } else { + update = .generic + } + return (view, update) + } + case let .navigate(index, collectionId): + var firstTime = true + return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: index, count: 300) + |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in + let update: StickerPacksCollectionUpdate + if firstTime { + firstTime = false + update = .navigate(index, collectionId) + } else { + update = .generic + } + return (view, update) + } + } + } + + let controllerInteraction = self.controllerInteraction + let inputNodeInteraction = self.inputNodeInteraction! + + let previousEntries = Atomic<([ChatMediaInputPanelEntry], [ChatMediaInputGridEntry])>(value: ([], [])) + let previousView = Atomic(value: nil) + let transitionQueue = Queue() + let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get()) + |> map { viewAndUpdate, trendingPacks, themeAndStrings -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in + let (view, viewUpdate) = viewAndUpdate + let previous = previousView.swap(view) + var update = viewUpdate + if previous === view { + update = .generic + } + let (theme, strings) = themeAndStrings + + var savedStickers: OrderedItemListView? + var recentStickers: OrderedItemListView? + for orderedView in view.orderedItemListsViews { + if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers { + recentStickers = orderedView + } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers { + savedStickers = orderedView + } + } + + var installedPacks = Set() + for info in view.collectionInfos { + installedPacks.insert(info.0) + } + + var hasUnreadTrending: Bool? + for pack in trendingPacks { + if hasUnreadTrending == nil { + hasUnreadTrending = false + } + if pack.unread { + hasUnreadTrending = true + break + } + } + + let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasGifs: false, hasUnreadTrending: hasUnreadTrending, theme: theme) + var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, strings: strings, theme: theme) + + if view.higher == nil { + var hasTopSeparator = true + if gridEntries.count == 1, case .search = gridEntries[0] { + hasTopSeparator = false + } + + var index = 0 + for item in trendingPacks { + if !installedPacks.contains(item.info.id) { + gridEntries.append(.trending(TrendingPanePackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread, topSeparator: hasTopSeparator))) + hasTopSeparator = true + index += 1 + } + } + } + + let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntries, gridEntries)) + return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty) + } + + self.disposable.set((transitions + |> deliverOnMainQueue).start(next: { [weak self] (view, panelTransition, panelFirstTime, gridTransition, gridFirstTime) in + if let strongSelf = self { + strongSelf.currentView = view + strongSelf.enqueuePanelTransition(panelTransition, firstTime: panelFirstTime, thenGridTransition: gridTransition, gridFirstTime: gridFirstTime) + if !strongSelf.initializedArrangement { + strongSelf.initializedArrangement = true + var currentPane = strongSelf.paneArrangement.panes[strongSelf.paneArrangement.currentIndex] + if view.entries.isEmpty { + //currentPane = .trending + } + if currentPane != strongSelf.paneArrangement.panes[strongSelf.paneArrangement.currentIndex] { + strongSelf.setCurrentPane(currentPane, transition: .immediate) + } + } + } + })) + + self.stickerPane.gridNode.visibleItemsUpdated = { [weak self] visibleItems in + if let strongSelf = self { + var topVisibleCollectionId: ItemCollectionId? + + if let topVisibleSection = visibleItems.topSectionVisible as? ChatMediaInputStickerGridSection { + topVisibleCollectionId = topVisibleSection.collectionId + } else if let topVisible = visibleItems.topVisible { + if let item = topVisible.1 as? ChatMediaInputStickerGridItem { + topVisibleCollectionId = item.index.collectionId + } else if let _ = topVisible.1 as? StickerPanePeerSpecificSetupGridItem { + topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0) + } + } + if let collectionId = topVisibleCollectionId { +// if strongSelf.inputNodeInteraction.highlightedItemCollectionId != collectionId { +// strongSelf.setHighlightedItemCollectionId(collectionId) +// } + } + + if let currentView = strongSelf.currentView, let (topIndex, topItem) = visibleItems.top, let (bottomIndex, bottomItem) = visibleItems.bottom { + if topIndex <= 10 && currentView.lower != nil { + let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: (topItem as! ChatMediaInputStickerGridItem).index)) + if strongSelf.currentStickerPacksCollectionPosition != position { + strongSelf.currentStickerPacksCollectionPosition = position + strongSelf.itemCollectionsViewPosition.set(.single(position)) + } + } else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil { + var position: StickerPacksCollectionPosition? + if let bottomItem = bottomItem as? ChatMediaInputStickerGridItem { + position = clipScrollPosition(.scroll(aroundIndex: bottomItem.index)) + } + + if let position = position, strongSelf.currentStickerPacksCollectionPosition != position { + strongSelf.currentStickerPacksCollectionPosition = position + strongSelf.itemCollectionsViewPosition.set(.single(position)) + } + } + } + } + } + + self.currentStickerPacksCollectionPosition = .initial + self.itemCollectionsViewPosition.set(.single(.initial)) + + self.stickerPane.inputNodeInteraction = self.inputNodeInteraction + + paneDidScrollImpl = { [weak self] pane, state, transition in + self?.updatePaneDidScroll(pane: pane, state: state, transition: transition) + } + + selectStickerImpl = { [weak self] fileReference, node, rect in + return self?.selectSticker?(fileReference, node, rect) ?? false + } + } + + deinit { + self.disposable.dispose() + } + + @objc private func cancelPressed() { + self.dismiss?() + } + + private func setHighlightedItemCollectionId(_ collectionId: ItemCollectionId) { + if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue { + if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs { + self.inputNodeInteraction.highlightedItemCollectionId = collectionId + } + } else { + self.inputNodeInteraction.highlightedStickerItemCollectionId = collectionId + if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .stickers { + self.inputNodeInteraction.highlightedItemCollectionId = collectionId + } + } + var ensuredNodeVisible = false + var firstVisibleCollectionId: ItemCollectionId? + self.listView.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMediaInputStickerPackItemNode { + if firstVisibleCollectionId == nil { + firstVisibleCollectionId = itemNode.currentCollectionId + } + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode { + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode { + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode { + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode { + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + self.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } + } + + if let currentView = self.currentView, let firstVisibleCollectionId = firstVisibleCollectionId, !ensuredNodeVisible { + let targetIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == collectionId }) + let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId }) + if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex { + let toRight = targetIndex > firstVisibleIndex + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil) + } + } + } + + private func setCurrentPane(_ pane: ChatMediaInputPaneType, transition: ContainedViewLayoutTransition, collectionIdHint: Int32? = nil) { + var transition = transition + + if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex { + let previousGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs + //let previousTrendingPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending + self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index) + let updatedGifPanelWasActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .gifs + //let updatedTrendingPanelIsActive = self.paneArrangement.panes[self.paneArrangement.currentIndex] == .trending + + /*if updatedTrendingPanelIsActive != previousTrendingPanelWasActive { + transition = .immediate + }*/ + +// if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { +// let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: transition, interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) +// self.updateAppearanceTransition(transition: transition) +// } +// if updatedGifPanelWasActive != previousGifPanelWasActive { +// self.gifPaneIsActiveUpdated(updatedGifPanelWasActive) +// } + switch pane { + case .gifs: + self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentGifs.rawValue, id: 0)) + case .stickers: + if let highlightedStickerCollectionId = self.inputNodeInteraction.highlightedStickerItemCollectionId { + self.setHighlightedItemCollectionId(highlightedStickerCollectionId) + } else if let collectionIdHint = collectionIdHint { + self.setHighlightedItemCollectionId(ItemCollectionId(namespace: collectionIdHint, id: 0)) + } + /*case .trending: + self.setHighlightedItemCollectionId(ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue, id: 0))*/ + } + /*if updatedTrendingPanelIsActive != previousTrendingPanelWasActive { + self.controllerInteraction.updateInputMode { current in + switch current { + case let .media(mode, _): + if updatedTrendingPanelIsActive { + return .media(mode: mode, expanded: .content) + } else { + return .media(mode: mode, expanded: nil) + } + default: + return current + } + } + }*/ + } else { +// if let (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible) = self.validLayout { +// let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .animated(duration: 0.25, curve: .spring), interfaceState: interfaceState, deviceMetrics: deviceMetrics, isVisible: isVisible) +// } + } + } + + private func currentCollectionListPanelOffset() -> CGFloat { + let paneOffsets = self.paneArrangement.panes.map { pane -> CGFloat in + return self.stickerPane.collectionListPanelOffset + } + + let mainOffset = paneOffsets[self.paneArrangement.currentIndex] + if self.paneArrangement.indexTransition.isZero { + return mainOffset + } else { + var sideOffset: CGFloat? + if self.paneArrangement.indexTransition < 0.0 { + if self.paneArrangement.currentIndex != 0 { + sideOffset = paneOffsets[self.paneArrangement.currentIndex - 1] + } + } else { + if self.paneArrangement.currentIndex != paneOffsets.count - 1 { + sideOffset = paneOffsets[self.paneArrangement.currentIndex + 1] + } + } + if let sideOffset = sideOffset { + let interpolator = CGFloat.interpolator() + let value = interpolator(mainOffset, sideOffset, abs(self.paneArrangement.indexTransition)) as! CGFloat + return value + } else { + return mainOffset + } + } + } + + func updateLayout(width: CGFloat, topInset: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) { + var searchMode: ChatMediaInputSearchMode? + + let wasVisible = true + + var displaySearch = false + let separatorHeight = UIScreenPixel + let panelHeight: CGFloat + + var isExpanded: Bool = true +// switch expanded { +// case .content: + panelHeight = maximumHeight +// case let .search(mode): +// panelHeight = maximumHeight +// displaySearch = true +// searchMode = mode +// } + self.stickerPane.collectionListPanelOffset = 0.0 + + var cancelSize = self.cancelButton.measure(CGSize(width: width, height: .greatestFiniteMagnitude)) + cancelSize.width += 16.0 * 2.0 + self.cancelButton.frame = CGRect(origin: CGPoint(x: width - cancelSize.width, y: topInset + 5.0), size: cancelSize) + + let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: width - cancelSize.width - 16.0 * 2.0), transition: transition) + transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: 16.0, y: topInset), size: controlSize)) + + if displaySearch { + if let searchContainerNode = self.searchContainerNode { + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight)) + if searchContainerNode.supernode != nil { + transition.updateFrame(node: searchContainerNode, frame: containerFrame) + searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition) + } else { + self.searchContainerNode = searchContainerNode + self.insertSubnode(searchContainerNode, belowSubnode: self.collectionListContainer) + searchContainerNode.frame = containerFrame + searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: .immediate) + var placeholderNode: PaneSearchBarPlaceholderNode? + if let searchMode = searchMode { + switch searchMode { + case .sticker: + self.stickerPane.gridNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { + placeholderNode = itemNode + } + } + default: + break + } + } + + if let placeholderNode = placeholderNode { + searchContainerNode.animateIn(from: placeholderNode, transition: transition, completion: { [weak self] in + }) + } + } + } + } + + let contentVerticalOffset: CGFloat = displaySearch ? -(inputPanelHeight + 41.0) : 0.0 + + let collectionListPanelOffset: CGFloat = 0.0 + + transition.updateFrame(view: self.blurView, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: maximumHeight))) + + transition.updateFrame(node: self.collectionListContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: maximumHeight + contentVerticalOffset - 41.0 - bottomInset), size: CGSize(width: width, height: max(0.0, 41.0 + UIScreenPixel + bottomInset)))) + transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: 41.0 + bottomInset))) + + self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0, height: width) + transition.updatePosition(node: self.listView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0 + 2.0)) + + let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) + let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0, height: width), insets: UIEdgeInsets(top: 4.0 + leftInset, left: 0.0, bottom: 4.0 + rightInset, right: 0.0), duration: duration, curve: curve) + + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + + var visiblePanes: [(ChatMediaInputPaneType, CGFloat)] = [] + + var paneIndex = 0 + for pane in self.paneArrangement.panes { + let paneOrigin = CGFloat(paneIndex - self.paneArrangement.currentIndex) * width - self.paneArrangement.indexTransition * width + if paneOrigin.isLess(than: width) && CGFloat(0.0).isLess(than: (paneOrigin + width)) { + visiblePanes.append((pane, paneOrigin)) + } + paneIndex += 1 + } + + for (pane, paneOrigin) in visiblePanes { + let paneFrame = CGRect(origin: CGPoint(x: paneOrigin + leftInset, y: topInset + 54.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight - topInset)) + switch pane { + case .stickers: + if self.stickerPane.supernode == nil { + self.insertSubnode(self.stickerPane, belowSubnode: self.collectionListContainer) + self.stickerPane.frame = CGRect(origin: CGPoint(x: width, y: topInset + 54.0), size: CGSize(width: width, height: panelHeight - topInset)) + } + if self.stickerPane.frame != paneFrame { + self.stickerPane.layer.removeAnimation(forKey: "position") + transition.updateFrame(node: self.stickerPane, frame: paneFrame) + } + default: + break + } + } + + self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight), topInset: 0.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: true, deviceMetrics: deviceMetrics, transition: transition) + + if !displaySearch, let searchContainerNode = self.searchContainerNode { + self.searchContainerNode = nil + self.searchContainerNodeLoadedDisposable.set(nil) + + var paneIsEmpty = false + var placeholderNode: PaneSearchBarPlaceholderNode? + if let searchMode = searchMode { + switch searchMode { + case .sticker: + paneIsEmpty = true + self.stickerPane.gridNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { + placeholderNode = itemNode + } + if let _ = itemNode as? ChatMediaInputStickerGridItemNode { + paneIsEmpty = false + } + } + default: + break + } + } + if let placeholderNode = placeholderNode { + searchContainerNode.animateOut(to: placeholderNode, animateOutSearchBar: !paneIsEmpty, transition: transition, completion: { [weak searchContainerNode] in + searchContainerNode?.removeFromSupernode() + }) + } else { + searchContainerNode.removeFromSupernode() + } + } + +// if let panRecognizer = self.panRecognizer, panRecognizer.isEnabled != !displaySearch { +// panRecognizer.isEnabled = !displaySearch +// } +// + + return (standardInputHeight, max(0.0, panelHeight - standardInputHeight)) + } + + private func enqueuePanelTransition(_ transition: ChatMediaInputPanelTransition, firstTime: Bool, thenGridTransition gridTransition: ChatMediaInputGridTransition, gridFirstTime: Bool) { + var options = ListViewDeleteAndInsertOptions() + if firstTime { + options.insert(.Synchronous) + options.insert(.LowLatency) + } else { + options.insert(.AnimateInsertion) + } + self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in + if let strongSelf = self { + strongSelf.enqueueGridTransition(gridTransition, firstTime: gridFirstTime) + if !strongSelf.didSetReady { + strongSelf.didSetReady = true + strongSelf._ready.set(.single(true)) + } + } + }) + } + + private func enqueueGridTransition(_ transition: ChatMediaInputGridTransition, firstTime: Bool) { + var itemTransition: ContainedViewLayoutTransition = .immediate + if transition.animated { + itemTransition = .animated(duration: 0.3, curve: .spring) + } + self.stickerPane.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset, updateOpaqueState: transition.updateOpaqueState), completion: { _ in }) + } + + private func updatePaneDidScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState, transition: ContainedViewLayoutTransition) { + pane.collectionListPanelOffset = 0.0 + +// let collectionListPanelOffset = self.currentCollectionListPanelOffset() +// +// self.updateAppearanceTransition(transition: transition) +// transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: self.collectionListPanel.bounds.size)) +// transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: self.collectionListSeparator.bounds.size)) +// transition.updatePosition(node: self.listView, position: CGPoint(x: self.listView.position.x, y: (41.0 - collectionListPanelOffset) / 2.0)) + } + + func animateIn() { + self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + let insets = layout.insets(options: [.statusBar]) + let height = layout.size.height + + let _ = self.updateLayout(width: layout.size.width, topInset: insets.top, leftInset: insets.left, rightInset: insets.right, bottomInset: insets.bottom, standardInputHeight: height, inputHeight: height, maximumHeight: height, inputPanelHeight: height, transition: transition, deviceMetrics: layout.deviceMetrics, isVisible: true) + } +} + +final class DrawingStickersScreen: ViewController { + private let context: AccountContext + private let selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? + + private var controllerNode: DrawingStickersScreenNode { + return self.displayNode as! DrawingStickersScreenNode + } + + private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? + + private var didPlayPresentationAnimation = false + + private let _ready = Promise() + override public var ready: Promise { + return self._ready + } + + public init(context: AccountContext, selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil) { + self.context = context + self.selectSticker = selectSticker + + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + + super.init(navigationBarPresentationData: nil) + + self.navigationPresentation = .modal + + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + + self.presentationDataDisposable = (context.sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previous = strongSelf.presentationData + strongSelf.presentationData = presentationData + + if previous.theme !== presentationData.theme || previous.strings !== presentationData.strings { + strongSelf.updatePresentationData() + } + } + }) + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.presentationDataDisposable?.dispose() + } + + private func updatePresentationData() { + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + } + + override public func loadDisplayNode() { + self.displayNode = DrawingStickersScreenNode( + context: self.context, + selectSticker: self.selectSticker.flatMap { [weak self] selectSticker in + return { file, sourceNode, sourceRect in + if selectSticker(file, sourceNode, sourceRect) { + self?.dismiss() + return true + } else { + return false + } + } + } + ) + (self.displayNode as! DrawingStickersScreenNode).dismiss = { [weak self] in + self?.dismiss() + } + self._ready.set(self.controllerNode.ready.get()) + + super.displayNodeDidLoad() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.didPlayPresentationAnimation { + self.didPlayPresentationAnimation = true + (self.displayNode as! DrawingStickersScreenNode).animateIn() + } + } + +// override public func inFocusUpdated(isInFocus: Bool) { +// self.controllerNode.inFocusUpdated(isInFocus: isInFocus) +// } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, navigationHeight: 0.0, transition: transition) + } +} diff --git a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift index dec88cecf3..8a31c87ffb 100644 --- a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift @@ -7,6 +7,7 @@ import SyncCore import LegacyComponents import FFMpegBinding import LocalMediaResources +import LegacyMediaPickerUI private final class AVURLAssetCopyItem: MediaResourceDataFetchCopyLocalItem { private let url: URL @@ -65,9 +66,9 @@ struct VideoConversionConfiguration { } static func with(appConfiguration: AppConfiguration) -> VideoConversionConfiguration { - #if DEBUG - return VideoConversionConfiguration(remuxToFMp4: true) - #endif +// #if DEBUG +// return VideoConversionConfiguration(remuxToFMp4: true) +// #endif if let data = appConfiguration.data, let conversion = data["video_conversion"] as? [String: Any] { let remuxToFMp4 = conversion["remux_fmp4"] as? Bool ?? VideoConversionConfiguration.defaultValue.remuxToFMp4 @@ -233,8 +234,8 @@ private final class FetchVideoLibraryMediaResourceContext { private let throttlingContext = FetchVideoLibraryMediaResourceContext() -public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibraryMediaResource) -> Signal { - return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) +public func fetchVideoLibraryMediaResource(account: Account, resource: VideoLibraryMediaResource) -> Signal { + return account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1) |> map { view in return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue @@ -286,6 +287,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr } } let updatedSize = Atomic(value: 0) + let entityRenderer = adjustments.flatMap { LegacyPaintEntityRenderer(account: account, adjustments: $0) } let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in var value = stat() if stat(path, &value) == 0 { @@ -299,7 +301,7 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) }*/ } - }))! + }), entityRenderer: entityRenderer)! let signalDisposable = signal.start(next: { next in if let result = next as? TGMediaVideoConversionResult { var value = stat() @@ -352,8 +354,8 @@ public func fetchVideoLibraryMediaResource(postbox: Postbox, resource: VideoLibr } } -func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideoMediaResource) -> Signal { - return postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) +func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideoMediaResource) -> Signal { + return account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]) |> take(1) |> map { view in return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue @@ -377,10 +379,11 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo } } let updatedSize = Atomic(value: 0) + let entityRenderer = adjustments.flatMap { LegacyPaintEntityRenderer(account: account, adjustments: $0) } let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in var value = stat() if stat(path, &value) == 0 { - /*if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { var range: Range? let _ = updatedSize.modify { updatedSize in range = updatedSize ..< Int(value.st_size) @@ -388,26 +391,26 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo } //print("size = \(Int(value.st_size)), range: \(range!)") subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) - }*/ + } } - }))! + }), entityRenderer: entityRenderer)! let signalDisposable = signal.start(next: { next in if let result = next as? TGMediaVideoConversionResult { var value = stat() if stat(result.fileURL.path, &value) == 0 { - if config.remuxToFMp4 { - let tempFile = TempBox.shared.tempFile(fileName: "video.mp4") - if FFMpegRemuxer.remux(result.fileURL.path, to: tempFile.path) { - let _ = try? FileManager.default.removeItem(atPath: result.fileURL.path) - subscriber.putNext(.moveTempFile(file: tempFile)) - } else { - TempBox.shared.dispose(tempFile) - subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) - } - } else { - subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) - } - /*if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) { +// if config.remuxToFMp4 { +// let tempFile = TempBox.shared.tempFile(fileName: "video.mp4") +// if FFMpegRemuxer.remux(result.fileURL.path, to: tempFile.path) { +// let _ = try? FileManager.default.removeItem(atPath: result.fileURL.path) +// subscriber.putNext(.moveTempFile(file: tempFile)) +// } else { +// TempBox.shared.dispose(tempFile) +// subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) +// } +// } else { +// subscriber.putNext(.moveLocalFile(path: result.fileURL.path)) +// } + if let data = try? Data(contentsOf: result.fileURL, options: [.mappedRead]) { var range: Range? let _ = updatedSize.modify { updatedSize in range = updatedSize ..< Int(value.st_size) @@ -417,7 +420,7 @@ func fetchLocalFileVideoMediaResource(postbox: Postbox, resource: LocalFileVideo subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) subscriber.putNext(.replaceHeader(data: data, range: 0 ..< 1024)) subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: true)) - }*/ + } } subscriber.putCompletion() } diff --git a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift index c0859cb01a..9c3afbb6e3 100644 --- a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift @@ -19,9 +19,9 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerC } }, fetchResource: { account, resource, ranges, _ in if let resource = resource as? VideoLibraryMediaResource { - return fetchVideoLibraryMediaResource(postbox: account.postbox, resource: resource) + return fetchVideoLibraryMediaResource(account: account, resource: resource) } else if let resource = resource as? LocalFileVideoMediaResource { - return fetchLocalFileVideoMediaResource(postbox: account.postbox, resource: resource) + return fetchLocalFileVideoMediaResource(account: account, resource: resource) } else if let resource = resource as? LocalFileGifMediaResource { return fetchLocalFileGifMediaResource(resource: resource) } else if let photoLibraryResource = resource as? PhotoLibraryMediaResource { From de7eede6ed8a64d8eea78c81b25fc194b0bfd863 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 23 May 2020 13:47:42 +0300 Subject: [PATCH 2/8] Fix build --- .../TelegramUI/Sources/DrawingStickersScreen.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 2be1d2579c..401b709275 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -194,7 +194,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { // controller.navigationPresentation = .modal // strongSelf.controllerInteraction.navigationController()?.pushViewController(controller) } - }, toggleSearch: { [weak self] value, searchMode in + }, toggleSearch: { [weak self] value, searchMode, query in if let strongSelf = self { if let searchMode = searchMode, value { var searchContainerNode: PaneSearchContainerNode? @@ -203,9 +203,14 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { } else { searchContainerNode = PaneSearchContainerNode(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction, inputNodeInteraction: strongSelf.inputNodeInteraction, mode: searchMode, trendingGifsPromise: Promise(nil), cancel: { self?.searchContainerNode?.deactivate() - self?.inputNodeInteraction.toggleSearch(false, nil) + self?.inputNodeInteraction.toggleSearch(false, nil, "") }) strongSelf.searchContainerNode = searchContainerNode + if !query.isEmpty { + DispatchQueue.main.async { + searchContainerNode?.updateQuery(query) + } + } } if let searchContainerNode = searchContainerNode { strongSelf.searchContainerNodeLoadedDisposable.set((searchContainerNode.ready @@ -644,6 +649,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { searchContainerNode.frame = containerFrame searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: .immediate) var placeholderNode: PaneSearchBarPlaceholderNode? + var anchorTop = CGPoint(x: 0.0, y: 0.0) + var anchorTopView: UIView = self.view if let searchMode = searchMode { switch searchMode { case .sticker: @@ -658,7 +665,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { } if let placeholderNode = placeholderNode { - searchContainerNode.animateIn(from: placeholderNode, transition: transition, completion: { [weak self] in + searchContainerNode.animateIn(from: placeholderNode, anchorTop: anchorTop, anhorTopView: anchorTopView, transition: transition, completion: { [weak self] in }) } } From a71196d839b49ab477f1006fced5c024377f2aae Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 23 May 2020 14:09:41 +0300 Subject: [PATCH 3/8] Fix video editor rendering orientation --- .../LegacyComponents/Sources/PGVideoMovie.m | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/submodules/LegacyComponents/Sources/PGVideoMovie.m b/submodules/LegacyComponents/Sources/PGVideoMovie.m index 49ecafb5c2..e27982e130 100755 --- a/submodules/LegacyComponents/Sources/PGVideoMovie.m +++ b/submodules/LegacyComponents/Sources/PGVideoMovie.m @@ -123,6 +123,21 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN #pragma mark - #pragma mark Initialization and teardown +- (UIInterfaceOrientation)orientationForTrack:(AVAsset *)asset +{ + AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; + CGSize size = [videoTrack naturalSize]; + CGAffineTransform txf = [videoTrack preferredTransform]; + + if (size.width == txf.tx && size.height == txf.ty) + return UIInterfaceOrientationLandscapeRight; + else if (txf.tx == 0 && txf.ty == 0) + return UIInterfaceOrientationLandscapeLeft; + else if (txf.tx == 0 && txf.ty == size.width) + return UIInterfaceOrientationPortraitUpsideDown; + else + return UIInterfaceOrientationPortrait; +} - (instancetype)initWithAsset:(AVAsset *)asset { @@ -554,7 +569,14 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN [currentTarget setInputSize:CGSizeMake(bufferWidth, bufferHeight) atIndex:targetTextureIndex]; [currentTarget setInputFramebuffer:outputFramebuffer atIndex:targetTextureIndex]; -// [currentTarget setInputRotation:kGPUImageRotateLeft atIndex:targetTextureIndex]; + UIInterfaceOrientation orientation = [self orientationForTrack:self.asset]; + if (orientation == UIInterfaceOrientationPortrait) { + [currentTarget setInputRotation:kGPUImageRotateRight atIndex:targetTextureIndex]; + } else if (orientation == UIInterfaceOrientationLandscapeRight) { + [currentTarget setInputRotation:kGPUImageRotate180 atIndex:targetTextureIndex]; + } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) { + [currentTarget setInputRotation:kGPUImageRotateLeft atIndex:targetTextureIndex]; + } } [outputFramebuffer unlock]; From a7ebc3bceabcfd8de928529b46e4e8b7ac4870b9 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 23 May 2020 14:40:49 +0300 Subject: [PATCH 4/8] Fix build --- Telegram/BUCK | 1 + submodules/LegacyComponents/BUCK | 8 ++++++++ submodules/LegacyMediaPickerUI/BUCK | 3 +++ 3 files changed, 12 insertions(+) diff --git a/Telegram/BUCK b/Telegram/BUCK index ff2f315899..0f38d7b3ae 100644 --- a/Telegram/BUCK +++ b/Telegram/BUCK @@ -45,6 +45,7 @@ framework_dependencies = [ ] resource_dependencies = [ + "//submodules/LegacyComponents:LegacyComponentsAssets", "//submodules/LegacyComponents:LegacyComponentsResources", "//submodules/TelegramUI:TelegramUIAssets", "//submodules/TelegramUI:TelegramUIResources", diff --git a/submodules/LegacyComponents/BUCK b/submodules/LegacyComponents/BUCK index 732e23594b..8e1dbc2173 100644 --- a/submodules/LegacyComponents/BUCK +++ b/submodules/LegacyComponents/BUCK @@ -8,6 +8,14 @@ apple_resource( visibility = ["PUBLIC"], ) +apple_asset_catalog( + name = 'LegacyComponentsAssets', + dirs = [ + "LegacyImages.xcassets", + ], + visibility = ["PUBLIC"], +) + static_library( name = "LegacyComponents", srcs = glob([ diff --git a/submodules/LegacyMediaPickerUI/BUCK b/submodules/LegacyMediaPickerUI/BUCK index 5a6e330a06..e1d07d37f2 100644 --- a/submodules/LegacyMediaPickerUI/BUCK +++ b/submodules/LegacyMediaPickerUI/BUCK @@ -21,6 +21,9 @@ static_library( "//submodules/SearchPeerMembers:SearchPeerMembers", "//submodules/SaveToCameraRoll:SaveToCameraRoll", "//submodules/LegacyMediaPickerUI/LegacyImageProcessors:LegacyImageProcessors", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/StickerResources:StickerResources", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", From fb656b8da14f396f1b3b018d91983e379921ff61 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 23 May 2020 16:17:07 +0300 Subject: [PATCH 5/8] Fix edited video thumbnails --- submodules/LegacyComponents/Sources/PGPhotoEditor.h | 4 ++++ submodules/LegacyComponents/Sources/PGPhotoEditor.m | 9 ++++++++- .../LegacyComponents/Sources/TGMediaAssetsController.m | 7 ++++++- .../LegacyComponents/Sources/TGMediaVideoConverter.m | 1 + .../LegacyComponents/Sources/TGPhotoEditorController.m | 6 +++++- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.h b/submodules/LegacyComponents/Sources/PGPhotoEditor.h index d0a5f31af3..16ad526d37 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.h +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.h @@ -31,6 +31,8 @@ @property (nonatomic, readonly) bool forVideo; +@property (nonatomic, assign) bool standalone; + - (instancetype)initWithOriginalSize:(CGSize)originalSize adjustments:(id)adjustments forVideo:(bool)forVideo enableStickers:(bool)enableStickers; - (void)cleanup; @@ -52,4 +54,6 @@ - (id)exportAdjustments; - (id)exportAdjustmentsWithPaintingData:(TGPaintingData *)paintingData; ++ (UIImage *)resultImageForImage:(UIImage *)image adjustments:(id)adjustments; + @end diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.m b/submodules/LegacyComponents/Sources/PGPhotoEditor.m index cb7eebfc56..85b8267be1 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.m +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.m @@ -221,7 +221,7 @@ - (void)processAnimated:(bool)animated capture:(bool)capture synchronous:(bool)synchronous completion:(void (^)(void))completion { - if (self.previewOutput == nil && ![_currentInput isKindOfClass:[GPUImageTextureInput class]]) + if (self.previewOutput == nil && !self.standalone) return; if (self.forVideo) { @@ -491,4 +491,11 @@ return tools; } ++ (UIImage *)resultImageForImage:(UIImage *)image adjustments:(id)adjustments { + PGPhotoEditor *editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:false enableStickers:true]; + editor.standalone = true; + [editor setImage:image forCropRect:adjustments.cropRect cropRotation:0.0 cropOrientation:adjustments.cropOrientation cropMirrored:adjustments.cropMirrored fullSize:false]; + return [editor currentResultImage]; +} + @end diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 44aee76b8b..5ec5042c07 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -27,6 +27,8 @@ #import #import +#import "PGPhotoEditor.h" + @interface TGMediaAssetsController () { TGMediaAssetsControllerIntent _intent; @@ -879,13 +881,16 @@ UIImage *(^cropVideoThumbnail)(UIImage *, CGSize, CGSize, bool) = ^UIImage *(UIImage *image, CGSize targetSize, CGSize sourceSize, bool resize) { - if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting) + if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied) { CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height); UIImage *paintingImage = adjustments.paintingData.stillImage; if (paintingImage == nil) { paintingImage = adjustments.paintingData.image; } + if (adjustments.toolsApplied) { + image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments]; + } return TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, targetSize, sourceSize, resize); } diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 4501fea098..052c8f7a0a 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -358,6 +358,7 @@ CIContext *ciContext = nil; if (adjustments.toolsApplied) { editor = [[PGPhotoEditor alloc] initWithOriginalSize:adjustments.originalSize adjustments:adjustments forVideo:true enableStickers:true]; + editor.standalone = true; ciContext = [CIContext contextWithEAGLContext:[[GPUImageContext sharedImageProcessingContext] context]]; } diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m index fbe55c5f8a..1d00575973 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m @@ -1456,7 +1456,7 @@ TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData]; bool hasChanges = !(_initialAdjustments == nil && [adjustments isDefaultValuesForAvatar:false] && adjustments.cropOrientation == UIImageOrientationUp); - if (adjustments.paintingData != nil || adjustments.hasPainting != _initialAdjustments.hasPainting) + if (adjustments.paintingData != nil || adjustments.hasPainting != _initialAdjustments.hasPainting || adjustments.toolsApplied) { [[SQueue concurrentDefaultQueue] dispatch:^ { @@ -1485,6 +1485,10 @@ 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; From 7d7ac395da91c6676fe00c72b8bbe28dec5e4bfd Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 23 May 2020 20:23:30 +0300 Subject: [PATCH 6/8] Video editor fixes --- .../LegacyComponents/Sources/GPUImageFilter.m | 23 +++ .../Sources/GPUImageFramebuffer.h | 8 +- .../Sources/GPUImageFramebuffer.m | 69 +++++++ .../LegacyComponents/Sources/GPUImageOutput.h | 3 + .../LegacyComponents/Sources/GPUImageOutput.m | 9 + .../Sources/GPUImageTextureInput.m | 2 +- .../Sources/GPUImageTextureOutput.m | 3 + .../LegacyComponents/Sources/PGPhotoEditor.m | 13 +- .../Sources/PGPhotoEnhanceLUTGenerator.m | 2 +- .../LegacyComponents/Sources/PGVideoMovie.h | 4 +- .../LegacyComponents/Sources/PGVideoMovie.m | 170 +++++++----------- .../TGMediaPickerGalleryVideoItemView.m | 25 ++- .../Sources/TGPhotoEditorInterfaceAssets.m | 10 +- 13 files changed, 216 insertions(+), 125 deletions(-) diff --git a/submodules/LegacyComponents/Sources/GPUImageFilter.m b/submodules/LegacyComponents/Sources/GPUImageFilter.m index c319f046ff..0a0d27835a 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFilter.m +++ b/submodules/LegacyComponents/Sources/GPUImageFilter.m @@ -200,6 +200,29 @@ NSString *const kGPUImagePassthroughFragmentShaderString = SHADER_STRING return image; } +- (CIImage *)newCIImageFromCurrentlyProcessedOutput { + // Give it three seconds to process, then abort if they forgot to set up the image capture properly + double timeoutForImageCapture = 3.0; + dispatch_time_t convertedTimeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutForImageCapture * NSEC_PER_SEC)); + + if (dispatch_semaphore_wait(imageCaptureSemaphore, convertedTimeout) != 0) + { + return NULL; + } + + GPUImageFramebuffer* framebuffer = [self framebufferForOutput]; + + usingNextFrameForImageCapture = NO; + dispatch_semaphore_signal(imageCaptureSemaphore); + + CIImage *image = [framebuffer newCIImageFromFramebufferContents]; + return image; +} + +- (void)commitImageCapture { + dispatch_semaphore_signal(imageCaptureSemaphore); +} + #pragma mark - #pragma mark Managing the display FBOs diff --git a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h index 1ee363c2f6..21b352edc2 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h +++ b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.h @@ -1,17 +1,12 @@ #import -#if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE #import #import #import -#else -#import -#import -#endif #import #import - +#import typedef struct GPUTextureOptions { GLenum minFilter; @@ -49,6 +44,7 @@ typedef struct GPUTextureOptions { // Image capture - (CGImageRef)newCGImageFromFramebufferContents; +- (CIImage *)newCIImageFromFramebufferContents; - (void)restoreRenderTarget; // Raw data bytes diff --git a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m index cf00a03f29..4f96d38ba9 100755 --- a/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m +++ b/submodules/LegacyComponents/Sources/GPUImageFramebuffer.m @@ -380,6 +380,75 @@ void dataProviderUnlockCallback (void *info, __unused const void *data, __unused return cgImageFromBytes; } +- (CIImage *)newCIImageFromFramebufferContents +{ + // a CGImage can only be created from a 'normal' color texture + NSAssert(self.textureOptions.internalFormat == GL_RGBA, @"For conversion to a CGImage the output texture format for this filter must be GL_RGBA."); + NSAssert(self.textureOptions.type == GL_UNSIGNED_BYTE, @"For conversion to a CGImage the type of the output texture of this filter must be GL_UNSIGNED_BYTE."); + + __block CIImage *ciImageFromBytes; + + runSynchronouslyOnVideoProcessingQueue(^{ + [GPUImageContext useImageProcessingContext]; + + NSUInteger totalBytesForImage = (int)_size.width * (int)_size.height * 4; + // It appears that the width of a texture must be padded out to be a multiple of 8 (32 bytes) if reading from it using a texture cache + + + GLubyte *rawImagePixels; + + CGDataProviderRef dataProvider = NULL; + if ([GPUImageContext supportsFastTextureUpload]) + { + NSUInteger paddedWidthOfImage = (NSUInteger)(CVPixelBufferGetBytesPerRow(renderTarget) / 4.0); + NSUInteger paddedBytesForImage = paddedWidthOfImage * (int)_size.height * 4; + + glFinish(); + CFRetain(renderTarget); // I need to retain the pixel buffer here and release in the data source callback to prevent its bytes from being prematurely deallocated during a photo write operation + [self lockForReading]; + rawImagePixels = (GLubyte *)CVPixelBufferGetBaseAddress(renderTarget); + +// dataProvider = CGDataProviderCreateWithData((__bridge_retained void*)self, rawImagePixels, paddedBytesForImage, dataProviderUnlockCallback); + [[GPUImageContext sharedFramebufferCache] addFramebufferToActiveImageCaptureList:self]; // In case the framebuffer is swapped out on the filter, need to have a strong reference to it somewhere for it to hang on while the image is in existence + + ciImageFromBytes = [[CIImage alloc] initWithCVPixelBuffer:renderTarget options:nil]; + + [self restoreRenderTarget]; + [self unlock]; + [[GPUImageContext sharedFramebufferCache] removeFramebufferFromActiveImageCaptureList:self]; + } +// else +// { +// [self activateFramebuffer]; +// rawImagePixels = (GLubyte *)malloc(totalBytesForImage); +// glReadPixels(0, 0, (int)_size.width, (int)_size.height, GL_RGBA, GL_UNSIGNED_BYTE, rawImagePixels); +// dataProvider = CGDataProviderCreateWithData(NULL, rawImagePixels, totalBytesForImage, dataProviderReleaseCallback); +// [self unlock]; // Don't need to keep this around anymore +// } + +// CGColorSpaceRef defaultRGBColorSpace = CGColorSpaceCreateDeviceRGB(); +// +// +// CIImage *image = [[CIImage alloc] initWithImageProvider:dataProvider size:<#(size_t)#> :<#(size_t)#> format:kCIFormatRGBA8 colorSpace:defaultRGBColorSpace options:<#(nullable NSDictionary *)#>] + +// if ([GPUImageContext supportsFastTextureUpload]) +// { +// cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, CVPixelBufferGetBytesPerRow(renderTarget), defaultRGBColorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst, dataProvider, NULL, NO, kCGRenderingIntentDefault); +// } +// else +// { +// cgImageFromBytes = CGImageCreate((int)_size.width, (int)_size.height, 8, 32, 4 * (int)_size.width, defaultRGBColorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast, dataProvider, NULL, NO, kCGRenderingIntentDefault); +// } + + // Capture image with current device orientation +// CGDataProviderRelease(dataProvider); +// CGColorSpaceRelease(defaultRGBColorSpace); + + }); + + return ciImageFromBytes; +} + - (void)restoreRenderTarget { #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE diff --git a/submodules/LegacyComponents/Sources/GPUImageOutput.h b/submodules/LegacyComponents/Sources/GPUImageOutput.h index 2f26d33a50..744a8c548a 100755 --- a/submodules/LegacyComponents/Sources/GPUImageOutput.h +++ b/submodules/LegacyComponents/Sources/GPUImageOutput.h @@ -2,6 +2,7 @@ #import "GPUImageFramebuffer.h" #import +#import void runOnMainQueueWithoutDeadlocking(void (^block)(void)); void runSynchronouslyOnVideoProcessingQueue(void (^block)(void)); @@ -79,6 +80,8 @@ void reportAvailableMemoryForGPUImage(NSString *tag); - (void)useNextFrameForImageCapture; - (CGImageRef)newCGImageFromCurrentlyProcessedOutput; +- (CIImage *)newCIImageFromCurrentlyProcessedOutput; +- (void)commitImageCapture; - (UIImage *)imageFromCurrentFramebuffer; - (UIImage *)imageFromCurrentFramebufferWithOrientation:(UIImageOrientation)imageOrientation; diff --git a/submodules/LegacyComponents/Sources/GPUImageOutput.m b/submodules/LegacyComponents/Sources/GPUImageOutput.m index 2d873f7243..20d769ddee 100755 --- a/submodules/LegacyComponents/Sources/GPUImageOutput.m +++ b/submodules/LegacyComponents/Sources/GPUImageOutput.m @@ -278,6 +278,15 @@ void reportAvailableMemoryForGPUImage(NSString *tag) return nil; } +- (CIImage *)newCIImageFromCurrentlyProcessedOutput { + return nil; +} + +- (void)commitImageCapture +{ + +} + - (BOOL)providesMonochromeOutput { return NO; diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureInput.m b/submodules/LegacyComponents/Sources/GPUImageTextureInput.m index 12cec3e377..b21fe87c99 100755 --- a/submodules/LegacyComponents/Sources/GPUImageTextureInput.m +++ b/submodules/LegacyComponents/Sources/GPUImageTextureInput.m @@ -28,7 +28,7 @@ - (instancetype)initWithCIImage:(CIImage *)ciImage { EAGLContext *context = [[GPUImageContext sharedImageProcessingContext] context]; - [EAGLContext setCurrentContext:[[GPUImageContext sharedImageProcessingContext] context]]; + [EAGLContext setCurrentContext:context]; GLsizei backingWidth = ciImage.extent.size.width; GLsizei backingHeight = ciImage.extent.size.height; diff --git a/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m index c99012be38..360e74ae41 100755 --- a/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m +++ b/submodules/LegacyComponents/Sources/GPUImageTextureOutput.m @@ -41,6 +41,9 @@ - (CIImage *)CIImageWithSize:(CGSize)size { + EAGLContext *context = [[GPUImageContext sharedImageProcessingContext] context]; + [EAGLContext setCurrentContext:context]; + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CIImage *image = [[CIImage alloc] initWithTexture:self.texture size:size flipped:true colorSpace:colorSpace]; CGColorSpaceRelease(colorSpace); diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.m b/submodules/LegacyComponents/Sources/PGPhotoEditor.m index 85b8267be1..becbeb24ee 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.m +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.m @@ -240,9 +240,13 @@ }]; } } else if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) { + if (capture) + [_finalFilter useNextFrameForImageCapture]; + [(GPUImageTextureInput *)currentInput processTextureWithFrameTime:kCMTimeZero synchronous:synchronous]; if (completion != nil) completion(); + [_finalFilter commitImageCapture]; } } synchronous:synchronous]; return; @@ -386,11 +390,12 @@ - (CIImage *)currentResultCIImage { __block CIImage *image = nil; GPUImageOutput *currentInput = _currentInput; - [self processAnimated:false capture:false synchronous:true completion:^ + [self processAnimated:false capture:true synchronous:true completion:^ { - if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) { - image = [_textureOutput CIImageWithSize:[(GPUImageTextureInput *)currentInput textureSize]]; - } + image = [_finalFilter newCIImageFromCurrentlyProcessedOutput]; +// if ([currentInput isKindOfClass:[GPUImageTextureInput class]]) { +// image = [_textureOutput CIImageWithSize:[(GPUImageTextureInput *)currentInput textureSize]]; +// } }]; return image; } diff --git a/submodules/LegacyComponents/Sources/PGPhotoEnhanceLUTGenerator.m b/submodules/LegacyComponents/Sources/PGPhotoEnhanceLUTGenerator.m index c87e7f58e2..cb27aa9fd9 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEnhanceLUTGenerator.m +++ b/submodules/LegacyComponents/Sources/PGPhotoEnhanceLUTGenerator.m @@ -172,7 +172,7 @@ const NSUInteger PGPhotoEnhanceSegments = 4; NSUInteger hMin = PGPhotoEnhanceHistogramBins - 1; for (NSUInteger j = 0; j < hMin; ++j) { - if (cdfs[j] != 0) + if (cdfs[i][j] != 0) hMin = j; } diff --git a/submodules/LegacyComponents/Sources/PGVideoMovie.h b/submodules/LegacyComponents/Sources/PGVideoMovie.h index 834a84a5a9..ac0f541090 100755 --- a/submodules/LegacyComponents/Sources/PGVideoMovie.h +++ b/submodules/LegacyComponents/Sources/PGVideoMovie.h @@ -6,18 +6,18 @@ @interface PGVideoMovie : GPUImageOutput @property (readwrite, retain) AVAsset *asset; +@property (readwrite, retain) AVPlayerItem *playerItem; @property (nonatomic, assign) bool shouldRepeat; @property (readonly, nonatomic) CGFloat progress; @property (readonly, nonatomic) AVAssetReader *assetReader; -@property (readonly, nonatomic) bool audioEncodingIsFinished; @property (readonly, nonatomic) bool videoEncodingIsFinished; - (instancetype)initWithAsset:(AVAsset *)asset; +- (instancetype)initWithPlayerItem:(AVPlayerItem *)playerItem; - (BOOL)readNextVideoFrameFromOutput:(AVAssetReaderOutput *)readerVideoTrackOutput; -- (BOOL)readNextAudioSampleFromOutput:(AVAssetReaderOutput *)readerAudioTrackOutput; - (void)startProcessing; - (void)endProcessing; - (void)cancelProcessing; diff --git a/submodules/LegacyComponents/Sources/PGVideoMovie.m b/submodules/LegacyComponents/Sources/PGVideoMovie.m index e27982e130..f994bd0c1c 100755 --- a/submodules/LegacyComponents/Sources/PGVideoMovie.m +++ b/submodules/LegacyComponents/Sources/PGVideoMovie.m @@ -89,8 +89,6 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN @interface PGVideoMovie () { - BOOL audioEncodingIsFinished, videoEncodingIsFinished; -// GPUImageMovieWriter *synchronizedMovieWriter; AVAssetReader *reader; AVPlayerItemVideoOutput *playerItemOutput; CADisplayLink *displayLink; @@ -116,6 +114,9 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN @end @implementation PGVideoMovie +{ + bool videoEncodingIsFinished; +} @synthesize asset = _asset; @synthesize shouldRepeat = _shouldRepeat; @@ -153,6 +154,20 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN return self; } +- (instancetype)initWithPlayerItem:(AVPlayerItem *)playerItem; +{ + if (!(self = [super init])) + { + return nil; + } + + [self yuvConversionSetup]; + + self.playerItem = playerItem; + + return self; +} + - (void)yuvConversionSetup { if ([GPUImageContext supportsFastTextureUpload]) @@ -221,7 +236,11 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN { if (_shouldRepeat) self->keepLooping = true; - [self processAsset]; + if (self.playerItem != nil) { + [self processPlayerItem]; + } else { + [self processAsset]; + } } - (AVAssetReader*)createAssetReader @@ -240,24 +259,9 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN } AVAssetReaderTrackOutput *readerVideoTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:[[self.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] outputSettings:outputSettings]; - readerVideoTrackOutput.alwaysCopiesSampleData = NO; + readerVideoTrackOutput.alwaysCopiesSampleData = false; [assetReader addOutput:readerVideoTrackOutput]; -// NSArray *audioTracks = [self.asset tracksWithMediaType:AVMediaTypeAudio]; -// BOOL shouldRecordAudioTrack = (([audioTracks count] > 0) && (self.audioEncodingTarget != nil) ); -// AVAssetReaderTrackOutput *readerAudioTrackOutput = nil; -// -// if (shouldRecordAudioTrack) -// { -// [self.audioEncodingTarget setShouldInvalidateAudioSampleWhenDone:YES]; -// -// // This might need to be extended to handle movies with more than one audio track -// AVAssetTrack* audioTrack = [audioTracks objectAtIndex:0]; -// readerAudioTrackOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:nil]; -// readerAudioTrackOutput.alwaysCopiesSampleData = NO; -// [assetReader addOutput:readerAudioTrackOutput]; -// } - return assetReader; } @@ -266,65 +270,59 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN reader = [self createAssetReader]; AVAssetReaderOutput *readerVideoTrackOutput = nil; - AVAssetReaderOutput *readerAudioTrackOutput = nil; - - audioEncodingIsFinished = YES; - for( AVAssetReaderOutput *output in reader.outputs ) { - if( [output.mediaType isEqualToString:AVMediaTypeAudio] ) { - audioEncodingIsFinished = NO; - readerAudioTrackOutput = output; - } - else if( [output.mediaType isEqualToString:AVMediaTypeVideo] ) { + + for (AVAssetReaderOutput *output in reader.outputs) { + if( [output.mediaType isEqualToString:AVMediaTypeVideo] ) { readerVideoTrackOutput = output; } } - if ([reader startReading] == NO) { + if (![reader startReading]) { return; } __unsafe_unretained PGVideoMovie *weakSelf = self; -// if (synchronizedMovieWriter != nil) -// { -// [synchronizedMovieWriter setVideoInputReadyCallback:^{ -// BOOL success = [weakSelf readNextVideoFrameFromOutput:readerVideoTrackOutput]; -// return success; -// }]; -// -// [synchronizedMovieWriter setAudioInputReadyCallback:^{ -// BOOL success = [weakSelf readNextAudioSampleFromOutput:readerAudioTrackOutput]; -// return success; -// }]; -// -// [synchronizedMovieWriter enableSynchronizationCallbacks]; -// } -// else -// { - while (reader.status == AVAssetReaderStatusReading && (!_shouldRepeat || keepLooping)) - { - [weakSelf readNextVideoFrameFromOutput:readerVideoTrackOutput]; + while (reader.status == AVAssetReaderStatusReading && (!_shouldRepeat || keepLooping)) + { + [weakSelf readNextVideoFrameFromOutput:readerVideoTrackOutput]; + } - if ((readerAudioTrackOutput) && (!audioEncodingIsFinished)) - { - [weakSelf readNextAudioSampleFromOutput:readerAudioTrackOutput]; - } + if (reader.status == AVAssetReaderStatusCompleted) { + + [reader cancelReading]; + if (keepLooping) { + reader = nil; + [self startProcessing]; + } else { + [weakSelf endProcessing]; } - if (reader.status == AVAssetReaderStatusCompleted) { - - [reader cancelReading]; + } +} - if (keepLooping) { - reader = nil; - [self startProcessing]; - } else { - [weakSelf endProcessing]; - } +- (void)processPlayerItem +{ + runSynchronouslyOnVideoProcessingQueue(^{ + displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)]; + [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + [displayLink setPaused:YES]; + dispatch_queue_t videoProcessingQueue = [GPUImageContext sharedContextQueue]; + NSMutableDictionary *pixBuffAttributes = [NSMutableDictionary dictionary]; + if ([GPUImageContext supportsFastTextureUpload]) { + [pixBuffAttributes setObject:@(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) forKey:(id)kCVPixelBufferPixelFormatTypeKey]; } -// } + else { + [pixBuffAttributes setObject:@(kCVPixelFormatType_32BGRA) forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + } + playerItemOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes]; + [playerItemOutput setDelegate:self queue:videoProcessingQueue]; + + [_playerItem addOutput:playerItemOutput]; + [playerItemOutput requestNotificationOfMediaDataChangeWithAdvanceInterval:0.1]; + }); } - (void)outputMediaDataWillChange:(AVPlayerItemOutput *)sender @@ -385,56 +383,20 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN CFRelease(sampleBufferRef); }); - return YES; + return true; } else { if (!keepLooping) { - videoEncodingIsFinished = YES; - if( videoEncodingIsFinished && audioEncodingIsFinished ) + videoEncodingIsFinished = true; + if (videoEncodingIsFinished) [self endProcessing]; } } } -// else if (synchronizedMovieWriter != nil) -// { -// if (reader.status == AVAssetReaderStatusCompleted) -// { -// [self endProcessing]; -// } -// } - return NO; + return false; } -- (BOOL)readNextAudioSampleFromOutput:(AVAssetReaderOutput *)readerAudioTrackOutput; -{ - if (reader.status == AVAssetReaderStatusReading && ! audioEncodingIsFinished) - { - CMSampleBufferRef audioSampleBufferRef = [readerAudioTrackOutput copyNextSampleBuffer]; - if (audioSampleBufferRef) - { - CFRelease(audioSampleBufferRef); - return YES; - } - else - { - if (!keepLooping) { - audioEncodingIsFinished = YES; - if (videoEncodingIsFinished && audioEncodingIsFinished) - [self endProcessing]; - } - } - } -// else if (synchronizedMovieWriter != nil) -// { -// if (reader.status == AVAssetReaderStatusCompleted || reader.status == AVAssetReaderStatusFailed || -// reader.status == AVAssetReaderStatusCancelled) -// { -// [self endProcessing]; -// } -// } - return NO; -} - (void)processMovieFrame:(CMSampleBufferRef)movieSampleBuffer; { @@ -692,10 +654,6 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN return reader; } -- (BOOL)audioEncodingIsFinished { - return audioEncodingIsFinished; -} - - (BOOL)videoEncodingIsFinished { return videoEncodingIsFinished; } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index e8d0082b1d..81773dc778 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -34,6 +34,8 @@ #import +#import "PGPhotoEditor.h" + @interface TGMediaPickerGalleryVideoItemView() { UIView *_containerView; @@ -1442,6 +1444,8 @@ return; AVAsset *avAsset = self.item.avAsset ?: _player.currentItem.asset; + TGMediaEditingContext *editingContext = self.item.editingContext; + id editableItem = self.item.editableMediaItem; SSignal *thumbnailsSignal = nil; if ([self.item.asset isKindOfClass:[TGMediaAsset class]] && ![self itemIsLivePhoto]) @@ -1452,7 +1456,26 @@ _requestingThumbnails = true; __weak TGMediaPickerGalleryVideoItemView *weakSelf = self; - [_thumbnailsDisposable setDisposable:[[thumbnailsSignal deliverOn:[SQueue mainQueue]] startWithNext:^(NSArray *images) + [_thumbnailsDisposable setDisposable:[[[thumbnailsSignal map:^NSArray *(NSArray *images) { + id adjustments = [editingContext adjustmentsForItem:editableItem]; + 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]] startWithNext:^(NSArray *images) { __strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf; if (strongSelf == nil) diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m index 4c0bf212de..262c029fb2 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorInterfaceAssets.m @@ -207,8 +207,10 @@ + (UIImage *)qualityIconForPreset:(TGMediaVideoConversionPreset)preset { - CGSize size = CGSizeMake(27.0f, 22.0f); - CGRect rect = CGRectInset(CGRectMake(0.0f, 0.0f, size.width, size.height), 1.0, 1.0); + CGFloat lineWidth = 2.0f - TGScreenPixel; + + CGSize size = CGSizeMake(28.0f, 22.0f); + CGRect rect = CGRectInset(CGRectMake(0.0f, 0.0f, size.width, size.height), lineWidth / 2.0, lineWidth / 2.0); UIGraphicsBeginImageContextWithOptions(size, false, 0.0f); CGContextRef context = UIGraphicsGetCurrentContext(); @@ -245,13 +247,13 @@ CGContextAddPath(context, path.CGPath); CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); - CGContextSetLineWidth(context, 2.0f - TGScreenPixel); + CGContextSetLineWidth(context, lineWidth); CGContextStrokePath(context); UIFont *font = [TGFont roundedFontOfSize:11]; CGSize textSize = [label sizeWithFont:font]; [[UIColor whiteColor] setFill]; - [label drawInRect:CGRectMake(floor(size.width - textSize.width) / 2.0f, 4.0f, textSize.width, textSize.height) withFont:font]; + [label drawInRect:CGRectMake((size.width - textSize.width) / 2.0f + TGScreenPixel, 4.0f, textSize.width, textSize.height) withFont:font]; UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); From f08016043ccc96f35931041e25ebd9488ab2cfb1 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 24 May 2020 12:52:02 +0300 Subject: [PATCH 7/8] Video editor fixes --- .../LegacyComponents/Sources/PGPhotoEditor.h | 2 + .../LegacyComponents/Sources/PGPhotoEditor.m | 15 ++++- .../LegacyComponents/Sources/PGVideoMovie.m | 39 +++++++----- .../TGMediaPickerGalleryVideoItemView.m | 61 +++++++++++++------ .../Sources/TGPhotoEditorPreviewView.m | 9 ++- 5 files changed, 87 insertions(+), 39 deletions(-) diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.h b/submodules/LegacyComponents/Sources/PGPhotoEditor.h index 16ad526d37..68a1408557 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.h +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.h @@ -39,6 +39,7 @@ - (void)setImage:(UIImage *)image forCropRect:(CGRect)cropRect cropRotation:(CGFloat)cropRotation cropOrientation:(UIImageOrientation)cropOrientation cropMirrored:(bool)cropMirrored fullSize:(bool)fullSize; - (void)setVideoAsset:(AVAsset *)asset; +- (void)setPlayerItem:(AVPlayerItem *)playerItem; - (void)setCIImage:(CIImage *)ciImage; - (void)processAnimated:(bool)animated completion:(void (^)(void))completion; @@ -51,6 +52,7 @@ - (SSignal *)histogramSignal; +- (void)importAdjustments:(id)adjustments; - (id)exportAdjustments; - (id)exportAdjustmentsWithPaintingData:(TGPaintingData *)paintingData; diff --git a/submodules/LegacyComponents/Sources/PGPhotoEditor.m b/submodules/LegacyComponents/Sources/PGPhotoEditor.m index becbeb24ee..d8d5d35f62 100644 --- a/submodules/LegacyComponents/Sources/PGPhotoEditor.m +++ b/submodules/LegacyComponents/Sources/PGPhotoEditor.m @@ -106,7 +106,7 @@ strongSelf->_histogramPipe.sink(histogram); }; - [self _importAdjustments:adjustments]; + [self importAdjustments:adjustments]; } return self; } @@ -172,6 +172,17 @@ _fullSize = true; } +- (void)setPlayerItem:(AVPlayerItem *)playerItem { + [_toolComposer invalidate]; + _currentProcessChain = nil; + + [_currentInput removeAllTargets]; + PGVideoMovie *movie = [[PGVideoMovie alloc] initWithPlayerItem:playerItem]; + _currentInput = movie; + + _fullSize = true; +} + - (void)setCIImage:(CIImage *)ciImage { [_toolComposer invalidate]; _currentProcessChain = nil; @@ -402,7 +413,7 @@ #pragma mark - Editor Values -- (void)_importAdjustments:(id)adjustments +- (void)importAdjustments:(id)adjustments { _initialAdjustments = adjustments; diff --git a/submodules/LegacyComponents/Sources/PGVideoMovie.m b/submodules/LegacyComponents/Sources/PGVideoMovie.m index f994bd0c1c..7a55f9a045 100755 --- a/submodules/LegacyComponents/Sources/PGVideoMovie.m +++ b/submodules/LegacyComponents/Sources/PGVideoMovie.m @@ -215,12 +215,11 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN { [playerItemOutput setDelegate:nil queue:nil]; - // Moved into endProcessing - //if (self.playerItem && (displayLink != nil)) - //{ - // [displayLink invalidate]; // remove from all run loops - // displayLink = nil; - //} + if (self.playerItem && (displayLink != nil)) + { + [displayLink invalidate]; + displayLink = nil; + } } #pragma mark - @@ -304,11 +303,14 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN - (void)processPlayerItem { - runSynchronouslyOnVideoProcessingQueue(^{ + dispatch_sync(dispatch_get_main_queue(), ^{ displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkCallback:)]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - [displayLink setPaused:YES]; + [displayLink setPaused:true]; + }); + + runSynchronouslyOnVideoProcessingQueue(^{ dispatch_queue_t videoProcessingQueue = [GPUImageContext sharedContextQueue]; NSMutableDictionary *pixBuffAttributes = [NSMutableDictionary dictionary]; if ([GPUImageContext supportsFastTextureUpload]) { @@ -335,7 +337,6 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN CFTimeInterval nextVSync = ([sender timestamp] + [sender duration]); CMTime outputItemTime = [playerItemOutput itemTimeForHostTime:nextVSync]; [self processPixelBufferAtTime:outputItemTime]; - } - (void)processPixelBufferAtTime:(CMTime)outputItemTime @@ -531,13 +532,19 @@ NSString *const kYUVVideoRangeConversionForLAFragmentShaderString = SHADER_STRIN [currentTarget setInputSize:CGSizeMake(bufferWidth, bufferHeight) atIndex:targetTextureIndex]; [currentTarget setInputFramebuffer:outputFramebuffer atIndex:targetTextureIndex]; - UIInterfaceOrientation orientation = [self orientationForTrack:self.asset]; - if (orientation == UIInterfaceOrientationPortrait) { - [currentTarget setInputRotation:kGPUImageRotateRight atIndex:targetTextureIndex]; - } else if (orientation == UIInterfaceOrientationLandscapeRight) { - [currentTarget setInputRotation:kGPUImageRotate180 atIndex:targetTextureIndex]; - } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) { - [currentTarget setInputRotation:kGPUImageRotateLeft atIndex:targetTextureIndex]; + AVAsset *asset = self.asset; + if (asset == nil) { + asset = self.playerItem.asset; + } + if (asset != nil) { + UIInterfaceOrientation orientation = [self orientationForTrack:asset]; + if (orientation == UIInterfaceOrientationPortrait) { + [currentTarget setInputRotation:kGPUImageRotateRight atIndex:targetTextureIndex]; + } else if (orientation == UIInterfaceOrientationLandscapeRight) { + [currentTarget setInputRotation:kGPUImageRotate180 atIndex:targetTextureIndex]; + } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) { + [currentTarget setInputRotation:kGPUImageRotateLeft atIndex:targetTextureIndex]; + } } } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index 81773dc778..4a2e070b02 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -29,6 +29,8 @@ #import "TGMediaPickerGalleryVideoScrubber.h" #import "TGMediaPickerScrubberHeaderView.h" +#import "TGPhotoEditorPreviewView.h" + #import #import "TGModernGalleryVideoContentView.h" @@ -67,7 +69,10 @@ UILabel *_fileInfoLabel; - TGModernGalleryVideoView *_videoView; +// TGModernGalleryVideoView *_videoView; + TGPhotoEditorPreviewView *_videoView; + PGPhotoEditor *_photoEditor; + UIImageView *_paintingImageView; NSTimer *_positionTimer; @@ -150,10 +155,10 @@ _curtainView.hidden = true; [_contentView addSubview:_curtainView]; - _actionButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 50, 50)]; + _actionButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, 60, 60)]; _actionButton.modernHighlight = true; - CGFloat circleDiameter = 50.0f; + CGFloat circleDiameter = 60.0f; static UIImage *highlightImage = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ @@ -433,6 +438,8 @@ if (adjustments.sendAsGif || ([strongSelf itemIsLivePhoto])) [strongSelf setPlayButtonHidden:true animated:false]; + + [_photoEditor importAdjustments:adjustments]; } else { @@ -976,19 +983,22 @@ if (_videoView != nil) { SMetaDisposable *currentAudioSession = _currentAudioSession; - if (currentAudioSession) - { - _videoView.deallocBlock = ^ - { - [[SQueue concurrentDefaultQueue] dispatch:^ - { - [currentAudioSession setDisposable:nil]; - }]; - }; - } - [_videoView cleanupPlayer]; +// if (currentAudioSession) +// { +// _videoView.deallocBlock = ^ +// { +// [[SQueue concurrentDefaultQueue] dispatch:^ +// { +// [currentAudioSession setDisposable:nil]; +// }]; +// }; +// } +// [_videoView cleanupPlayer]; [_videoView removeFromSuperview]; _videoView = nil; + + [_photoEditor cleanup]; + _photoEditor = nil; } self.isPlaying = false; @@ -1045,15 +1055,26 @@ strongSelf->_didPlayToEndObserver = [[TGObserverProxy alloc] initWithTarget:strongSelf targetSelector:@selector(playerItemDidPlayToEndTime:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; - strongSelf->_videoView = [[TGModernGalleryVideoView alloc] initWithFrame:strongSelf->_playerView.bounds player:strongSelf->_player]; - strongSelf->_videoView.frame = strongSelf->_imageView.frame; - strongSelf->_videoView.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; - strongSelf->_videoView.playerLayer.opaque = false; - strongSelf->_videoView.playerLayer.backgroundColor = nil; - [strongSelf->_playerContainerView insertSubview:strongSelf->_videoView belowSubview:strongSelf->_paintingImageView]; +// strongSelf->_videoView = [[TGModernGalleryVideoView alloc] initWithFrame:strongSelf->_playerView.bounds player:strongSelf->_player]; +// strongSelf->_videoView.frame = strongSelf->_imageView.frame; +// strongSelf->_videoView.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; +// strongSelf->_videoView.playerLayer.opaque = false; +// strongSelf->_videoView.playerLayer.backgroundColor = nil; +// [strongSelf->_playerContainerView insertSubview:strongSelf->_videoView belowSubview:strongSelf->_paintingImageView]; TGVideoEditAdjustments *adjustments = (TGVideoEditAdjustments *)[strongSelf.item.editingContext adjustmentsForItem:strongSelf.item.editableMediaItem]; + strongSelf->_videoView = [[TGPhotoEditorPreviewView alloc] initWithFrame:strongSelf->_imageView.frame]; + [strongSelf->_playerContainerView insertSubview:strongSelf->_videoView belowSubview:strongSelf->_paintingImageView]; + + [strongSelf->_videoView setNeedsTransitionIn]; + [strongSelf->_videoView performTransitionInIfNeeded]; + + strongSelf->_photoEditor = [[PGPhotoEditor alloc] initWithOriginalSize:_videoDimensions adjustments:adjustments forVideo:true enableStickers:true]; + strongSelf->_photoEditor.previewOutput = strongSelf->_videoView; + [strongSelf->_photoEditor setPlayerItem:playerItem]; + [strongSelf->_photoEditor processAnimated:false completion:nil]; + [strongSelf _seekToPosition:adjustments.trimStartValue manual:false]; if (adjustments.trimEndValue > DBL_EPSILON) [strongSelf updatePlayerRange:adjustments.trimEndValue]; diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m b/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m index ad373bcab0..004c437d20 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorPreviewView.m @@ -279,12 +279,19 @@ - (void)layoutSubviews { + if (self.bounds.size.width <= 0.0f || _cropRect.size.width <= 0.0f || _originalSize.width <= 0.0f || self.paintingView.image == nil) { + return; + } + CGFloat rotation = TGRotationForOrientation(_cropOrientation); _paintingContainerView.transform = CGAffineTransformMakeRotation(rotation); _paintingContainerView.frame = self.bounds; CGFloat width = TGOrientationIsSideward(_cropOrientation, NULL) ? self.frame.size.height : self.frame.size.width; - CGFloat ratio = width / _cropRect.size.width; + CGFloat ratio = 1.0; + if (_cropRect.size.width > 0.0) { + ratio = width / _cropRect.size.width; + } rotation = _cropRotation; From 29fe4b3b09ce2560da87211171f0363c340162ee Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 24 May 2020 20:03:03 +0300 Subject: [PATCH 8/8] Send photos with animated stickers as GIFs --- .../LegacyComponents/TGMediaVideoConverter.h | 2 + .../LegacyComponents/TGVideoEditAdjustments.h | 4 +- .../blank_1080p.mp4 | Bin 0 -> 58848 bytes .../Sources/TGMediaAssetsController.m | 42 ++++- .../Sources/TGMediaVideoConverter.m | 162 ++++++++++-------- .../Sources/TGVideoEditAdjustments.m | 22 +++ .../Sources/LegacyMediaPickers.swift | 35 ++-- .../Sources/LegacyPaintStickersContext.swift | 5 +- .../Sources/FetchVideoMediaResource.swift | 47 +++-- 9 files changed, 220 insertions(+), 99 deletions(-) create mode 100644 submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/blank_1080p.mp4 diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h index 6f9446c8c7..ad993b9001 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaVideoConverter.h @@ -20,6 +20,8 @@ + (SSignal *)convertAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher inhibitAudio:(bool)inhibitAudio entityRenderer:(id)entityRenderer; + (SSignal *)hashForAVAsset:(AVAsset *)avAsset adjustments:(TGMediaVideoEditAdjustments *)adjustments; ++ (SSignal *)renderUIImage:(UIImage *)image adjustments:(TGMediaVideoEditAdjustments *)adjustments watcher:(TGMediaVideoFileWatcher *)watcher entityRenderer:(id)entityRenderer; + + (NSUInteger)estimatedSizeForPreset:(TGMediaVideoConversionPreset)preset duration:(NSTimeInterval)duration hasAudio:(bool)hasAudio; + (TGMediaVideoConversionPreset)bestAvailablePresetForDimensions:(CGSize)dimensions; + (CGSize)_renderSizeWithCropSize:(CGSize)cropSize; diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h index 4e6fc32d7e..0979089e32 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGVideoEditAdjustments.h @@ -1,6 +1,8 @@ #import #import +@class PGPhotoEditorValues; + typedef enum { TGMediaVideoConversionPresetCompressedDefault, @@ -29,7 +31,7 @@ typedef enum - (instancetype)editAdjustmentsWithPreset:(TGMediaVideoConversionPreset)preset maxDuration:(NSTimeInterval)maxDuration; + (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize preset:(TGMediaVideoConversionPreset)preset; - ++ (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values; + (instancetype)editAdjustmentsWithDictionary:(NSDictionary *)dictionary; + (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize diff --git a/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/blank_1080p.mp4 b/submodules/LegacyComponents/Resources/LegacyComponentsResources.bundle/blank_1080p.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..17bacbefd3487b548de3d277d1d98a1c27472b44 GIT binary patch literal 58848 zcmeI4e{3XW8OPu4^^Q`0m3Y7rK;{-uF1p>B{n6c7yU<>Hm%GH}YFiPF#+#jax1Dr% zXK!Zu`J|AriePf`5p?ASxO-qa4N%J>u2pogdqM zy4y)_Y5%zJOm;f+y!-zAyzlpYp4n~77~5BIPc~|eOp3uFu5c=hT~?`C)&e9|YYVf6 zc<C58rLBkQ-~ZVQs_S=?N*PcAd|EDIz%-YP4{ zninja$8D{WElp&kDac^7$@YpswgZkY+pzr3NH+FRAX#IBWT#=L)`GUz0rs!~ktPNW zhs+(r)5@;p<>g!1$UrY#_l{i0V9oZv^aShu-LtzPrRUlQ_r%0>ZPXlW39#Zj%SP

CAj^Yup$zW&gOZ+`X5fBC`phwol$ zX!;y?r81u}>&5t+Pd*iwOrse8M7AWC8e@FcJaWqBQ>X5k)=$mpsu92YuKwYr!cwhK z7L7T=KHGbH(_mctg6cGy$8q&86&MHlfQ(M#`y!N><6s z*$+!8SxG15Oj1@8N~$2|3n@hkj`&^u(y(n*3X|i9gT}xr#%Eo(QAj2i7Z(Q?(}R{h zlT=hyP0FccDwTj73FlRBwwGbEmQ&7{R*JFg)y6JE{D*A7vo}yB;UCcAb4&pl zZnb1_T+Mc;VP#I~S{17BHl2>|dmWiFPl_b|0+|uX8LOwcPjKk~>8m3X;N?OSd@u5Uc)$@sroX;o9Tu#AuqA5D(Wj&Q`(t@gv z)}faW6NgopP(`L}=-FIeOAM*9mdK=YS|XoSD+!saxja`Z+EA{^5~}Ffi*im)mGh|t zPaEk(CO@PmwCqqOQK@9IX*He6R1C%2a-?DCILw+cZ;N@V2jUe;3tDv^fT%DAe`a9! z5$TRkQ%R~~6OSjw&J|X(zK#S&ZL1+>Tx9bmEaewa!fj|(Z?SY}p+m_QPLX|B zrvo!}#B^NCKIsATH~T4m!nb_0YI^lblDDlDG+j(dF|K=ijNuO`eADc(a<;tqV8;9= z7H2s$(7JIA6|zHxRK8{7YG!ttL(V!ED}%Xzh&RxzRWvB9RCDrD5{85^1^9d@D_4k1Yr9&ug36RfRm z8G2qR=PTuME-|E)LCt!mk|>wCk|=|UGpamPNvqi{3?2MYc1MT4onzkeCfHn^9jAMP z4{m1eyp+8t(En885PVhvUapgiZC0{A>EXzSlOkjbRXv=9U222w#eVDU``|ha6Bdfo zvK<`MW;WRlAm%7^Qamhyikwoiurv(N0#B)lat0iaQclTwT2srqQdwUbqLzwn)h56( z7UN>$D#%7EBg3l#XXt{x0gAk?wbh(8&6z7{_8hmJV*DPf&M}d2U?GvCbv*ol$~df@``}9wzvG9j&^} zgwU~xN0&ksHc1GT5(ckm^Hn!~*BEq4Qhe1^&1NA#p{1JPw+J6H!zb9W8hp|QS2&(~Z2!P+_V7(>PkSc)@CfAAmWjkV0ZV)j zOh-uIm3`q|+cmaEa%zLZNKS|e$qC_j<$7ec7_m1=O)WTrTT&BZLTW-dBGmK_sR^O- z3{n$fLTW-dqC&mjlAI7Ll2bblk`rPQ;hg?WYC@<;P3<^HO^8W^n*K*>La0bh?KntH zh)Gna_Z-Oyu_8IO;~+U9CK1l*6;cyIMQUotL25!wBGmLBQWHW&YHG(pYC=q+LOuG* z38C^0`j8w!Avqx&5zdL!gh-K^I^ZBRAt(`Q`aOku2$g4$Z$eB+O$bL+sP`Jl39%wM zwc{W;Atn*di9$U@$~VY4AtIde1n`5faPx4I&m1mHe5ED`p!Vwkfogq0PRwSo(93&^iB*Hm8 zLux{(NKNfHNKJ@IgqrA&oDeDBpimD%AvGZ!QK8;1$T=ZaoM9AtI0}FX3O}=hO&eAAFdx8{vAxtBl=poU#3Ie+QhyaDGf=It@Q!1Ms6q5+=R2 zO5lnmf<**~01+SpM1TnF0D%}AUFo^@@vC0GkcV)ktKcN|g)R$r*WC7(ip659y}MuN zd%4?jOm>TI`^VDN42o`e5H2SOtG!pfaCz4&;qIrqT|-?@+_o2t^^C6c?*Hh(Kc2eq z#mBlG4!_hv#Cl?@d#-!r;E&h3MUZ~A+mhQprCnF(QMi#Pz*@M4D2%S`k&o|x=W`cU z#itovz4)c=D(Sln)^^Z`uLuc!_=<2ueE3R#+KEW{2B`@_AvGZ!QQr){1`lx%wD#Tc z$icU}4q9)0wJW#YT`;gir927;hytvI1W_1W=^J@(2d)0T(4VEX5L@k6i2_h45{1!~ z{)yw#1An~m-TS+Bc{iTxw&V_ZzKg9^c*-jeJu)`bb-Zwse2F6fWFCd1L;==9i71S& vTzQ=Kh1lxVAE&hd6h285fWl{p!syD?4cZrCs{@?Y0#GoC0#K;Ew<-J|nEH<+ literal 0 HcmV?d00001 diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index 5ec5042c07..094e30a538 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -821,10 +821,48 @@ NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; dict[@"type"] = @"editedPhoto"; dict[@"image"] = image; - if (adjustments.paintingData.stickers.count > 0) dict[@"stickers"] = adjustments.paintingData.stickers; + bool animated = false; + for (TGPhotoPaintEntity *entity in adjustments.paintingData.entities) { + if (entity.animated) { + animated = true; + break; + } + } + + if (animated) { + dict[@"isAnimation"] = @true; + if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) { + dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)adjustments]; + } else { + dict[@"adjustments"] = adjustments; + } + + NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"gifvideo_%x.jpg", (int)arc4random()]]; + NSData *data = UIImageJPEGRepresentation(image, 0.8); + [data writeToFile:filePath atomically:true]; + dict[@"url"] = [NSURL fileURLWithPath:filePath]; + + + if ([adjustments cropAppliedForAvatar:false] || adjustments.hasPainting || adjustments.toolsApplied) + { + CGRect scaledCropRect = CGRectMake(adjustments.cropRect.origin.x * image.size.width / adjustments.originalSize.width, adjustments.cropRect.origin.y * image.size.height / adjustments.originalSize.height, adjustments.cropRect.size.width * image.size.width / adjustments.originalSize.width, adjustments.cropRect.size.height * image.size.height / adjustments.originalSize.height); + UIImage *paintingImage = adjustments.paintingData.stillImage; + if (paintingImage == nil) { + paintingImage = adjustments.paintingData.image; + } + if (adjustments.toolsApplied) { + image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments]; + } + UIImage *thumbnailImage = TGPhotoEditorCrop(image, paintingImage, adjustments.cropOrientation, 0, scaledCropRect, adjustments.cropMirrored, TGScaleToFill(asset.dimensions, CGSizeMake(384, 384)), asset.dimensions, true); + if (thumbnailImage != nil) { + dict[@"previewImage"] = thumbnailImage; + } + } + } + if (timer != nil) dict[@"timer"] = timer; else if (groupedId != nil && !hasAnyTimers) @@ -902,7 +940,7 @@ CGSize imageSize = TGFillSize(asset.dimensions, CGSizeMake(384, 384)); return [[TGMediaAssetImageSignals videoThumbnailForAVAsset:avAsset size:imageSize timestamp:CMTimeMakeWithSeconds(adjustments.trimStartValue, NSEC_PER_SEC)] map:^UIImage *(UIImage *image) { - return cropVideoThumbnail(image, TGScaleToFill(asset.dimensions, CGSizeMake(256, 256)), asset.dimensions, true); + return cropVideoThumbnail(image, TGScaleToFill(asset.dimensions, CGSizeMake(384, 384)), asset.dimensions, true); }]; }]; diff --git a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m index 052c8f7a0a..07ae33cc5a 100644 --- a/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m +++ b/submodules/LegacyComponents/Sources/TGMediaVideoConverter.m @@ -42,6 +42,7 @@ @property (nonatomic, readonly) bool succeed; - (instancetype)initWithAssetReaderOutput:(AVAssetReaderOutput *)assetReaderOutput assetWriterInput:(AVAssetWriterInput *)assetWriterInput; +- (instancetype)initWithUIImage:(UIImage *)image duration:(NSTimeInterval)duration assetWriterInput:(AVAssetWriterInput *)assetWriterInput; - (void)startWithTimeRange:(CMTimeRange)timeRange progressBlock:(void (^)(CGFloat progress))progressBlock completionBlock:(void (^)(void))completionBlock; - (void)cancel; @@ -153,7 +154,7 @@ } } - if (![self setupAssetReaderWriterForItem:avAsset outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error]) + if (![self setupAssetReaderWriterForAVAsset:avAsset image:nil outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:inhibitAudio conversionContext:context error:&error]) { [subscriber putError:error]; return; @@ -220,65 +221,72 @@ SAtomic *context = [[SAtomic alloc] initWithValue:[TGMediaVideoConversionContext contextWithQueue:queue subscriber:subscriber]]; NSURL *outputUrl = [self _randomTemporaryURL]; - [queue dispatch:^ + NSString *path = TGComponentsPathForResource(@"blank_1080p", @"mp4"); + AVAsset *avAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:path] options:nil]; + + NSArray *requiredKeys = @[ @"tracks", @"duration", @"playable" ]; + [avAsset loadValuesAsynchronouslyForKeys:requiredKeys completionHandler:^ { - if (((TGMediaVideoConversionContext *)context.value).cancelled) - return; - - TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetAnimation; - - NSError *error = nil; - - NSString *outputPath = outputUrl.path; - NSFileManager *fileManager = [NSFileManager defaultManager]; - if ([fileManager fileExistsAtPath:outputPath]) + [queue dispatch:^ { - [fileManager removeItemAtPath:outputPath error:&error]; - if (error != nil) + if (((TGMediaVideoConversionContext *)context.value).cancelled) + return; + + TGMediaVideoConversionPreset preset = TGMediaVideoConversionPresetAnimation; + + NSError *error = nil; + + NSString *outputPath = outputUrl.path; + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:outputPath]) + { + [fileManager removeItemAtPath:outputPath error:&error]; + if (error != nil) + { + [subscriber putError:error]; + return; + } + } + + if (![self setupAssetReaderWriterForAVAsset:avAsset image:image outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:true conversionContext:context error:&error]) { [subscriber putError:error]; return; } - } - - if (![self setupAssetReaderWriterForItem:image outputURL:outputUrl preset:preset entityRenderer:entityRenderer adjustments:adjustments inhibitAudio:true conversionContext:context error:&error]) - { - [subscriber putError:error]; - return; - } - - TGDispatchAfter(1.0, queue._dispatch_queue, ^ - { - if (watcher != nil) - [watcher setupWithFileURL:outputUrl]; - }); - - [self processWithConversionContext:context completionBlock:^ - { - TGMediaVideoConversionContext *resultContext = context.value; - [resultContext.imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(__unused CMTime requestedTime, CGImageRef _Nullable image, __unused CMTime actualTime, AVAssetImageGeneratorResult result, __unused NSError * _Nullable error) + + TGDispatchAfter(1.0, queue._dispatch_queue, ^ { - UIImage *coverImage = nil; - if (result == AVAssetImageGeneratorSucceeded) - coverImage = [UIImage imageWithCGImage:image]; - - __block TGMediaVideoConversionResult *contextResult = nil; - [context modify:^id(TGMediaVideoConversionContext *resultContext) + if (watcher != nil) + [watcher setupWithFileURL:outputUrl]; + }); + + [self processWithConversionContext:context completionBlock:^ + { + TGMediaVideoConversionContext *resultContext = context.value; + [resultContext.imageGenerator generateCGImagesAsynchronouslyForTimes:@[ [NSValue valueWithCMTime:kCMTimeZero] ] completionHandler:^(__unused CMTime requestedTime, CGImageRef _Nullable image, __unused CMTime actualTime, AVAssetImageGeneratorResult result, __unused NSError * _Nullable error) { - id liveUploadData = nil; - if (watcher != nil) - liveUploadData = [watcher fileUpdated:true]; + UIImage *coverImage = nil; + if (result == AVAssetImageGeneratorSucceeded) + coverImage = [UIImage imageWithCGImage:image]; - contextResult = [TGMediaVideoConversionResult resultWithFileURL:outputUrl fileSize:0 duration:CMTimeGetSeconds(resultContext.timeRange.duration) dimensions:resultContext.dimensions coverImage:coverImage liveUploadData:liveUploadData]; - return [resultContext finishedContext]; + __block TGMediaVideoConversionResult *contextResult = nil; + [context modify:^id(TGMediaVideoConversionContext *resultContext) + { + id liveUploadData = nil; + if (watcher != nil) + liveUploadData = [watcher fileUpdated:true]; + + contextResult = [TGMediaVideoConversionResult resultWithFileURL:outputUrl fileSize:0 duration:CMTimeGetSeconds(resultContext.timeRange.duration) dimensions:resultContext.dimensions coverImage:coverImage liveUploadData:liveUploadData]; + return [resultContext finishedContext]; + }]; + + [subscriber putNext:contextResult]; + [subscriber putCompletion]; }]; - - [subscriber putNext:contextResult]; - [subscriber putCompletion]; }]; }]; }]; - + return [[SBlockDisposable alloc] initWithBlock:^ { [queue dispatch:^ @@ -314,7 +322,7 @@ return outputDimensions; } -+ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext ++ (AVAssetReaderVideoCompositionOutput *)setupVideoCompositionOutputWithAVAsset:(AVAsset *)avAsset image:(UIImage *)image composition:(AVMutableComposition *)composition videoTrack:(AVAssetTrack *)videoTrack preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments timeRange:(CMTimeRange)timeRange outputSettings:(NSDictionary **)outputSettings dimensions:(CGSize *)dimensions conversionContext:(SAtomic *)conversionContext { CGSize transformedSize = CGRectApplyAffineTransform((CGRect){CGPointZero, videoTrack.naturalSize}, videoTrack.preferredTransform).size;; CGRect transformedRect = CGRectMake(0, 0, transformedSize.width, transformedSize.height); @@ -325,6 +333,8 @@ CGRect cropRect = hasCropping ? CGRectIntegral(adjustments.cropRect) : transformedRect; if (cropRect.size.width < FLT_EPSILON || cropRect.size.height < FLT_EPSILON) cropRect = transformedRect; + if (image != nil) + cropRect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height); CGSize maxDimensions = [TGMediaVideoConversionPresetSettings maximumSizeForPreset:preset]; CGSize outputDimensions = TGFitSizeF(cropRect.size, maxDimensions); @@ -361,11 +371,22 @@ editor.standalone = true; ciContext = [CIContext contextWithEAGLContext:[[GPUImageContext sharedImageProcessingContext] context]]; } - + + CIImage *backgroundCIImage = nil; + if (image != nil) { + backgroundCIImage = [[CIImage alloc] initWithImage:image]; + } + __block CIImage *overlayCIImage = nil; videoComposition = [AVMutableVideoComposition videoCompositionWithAsset:avAsset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) { __block CIImage *resultImage = request.sourceImage; + if (backgroundCIImage != nil) { + resultImage = backgroundCIImage; + } + + CGSize size = resultImage.extent.size; + if (editor != nil) { [editor setCIImage:resultImage]; resultImage = editor.currentResultCIImage; @@ -374,14 +395,14 @@ if (overlayImage != nil && overlayImage.size.width > 0.0) { if (overlayCIImage == nil) { overlayCIImage = [[CIImage alloc] initWithImage:overlayImage]; - CGFloat scale = request.sourceImage.extent.size.width / overlayCIImage.extent.size.width; + CGFloat scale = size.width / overlayCIImage.extent.size.width; overlayCIImage = [overlayCIImage imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)]; } resultImage = [overlayCIImage imageByCompositingOverImage:resultImage]; } if (entityRenderer != nil) { - [entityRenderer entitiesForTime:request.compositionTime size:request.sourceImage.extent.size completion:^(NSArray *images) { + [entityRenderer entitiesForTime:request.compositionTime size:size completion:^(NSArray *images) { for (CIImage *image in images) { resultImage = [image imageByCompositingOverImage:resultImage]; } @@ -422,10 +443,13 @@ if (!CMTIME_IS_VALID(videoComposition.frameDuration)) videoComposition.frameDuration = CMTimeMake(1, 30); + if (image != nil) + videoComposition.frameDuration = CMTimeMake(1, 30); + videoComposition.renderSize = [self _renderSizeWithCropSize:cropRect.size rotateSideward:TGOrientationIsSideward(adjustments.cropOrientation, NULL)]; if (videoComposition.renderSize.width < FLT_EPSILON || videoComposition.renderSize.height < FLT_EPSILON) return nil; - + if (overlayImage != nil && entityRenderer == nil) { CALayer *parentLayer = [CALayer layer]; @@ -481,14 +505,12 @@ return output; } -+ (bool)setupAssetReaderWriterForItem:(id)item outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error ++ (bool)setupAssetReaderWriterForAVAsset:(AVAsset *)avAsset image:(UIImage *)image outputURL:(NSURL *)outputURL preset:(TGMediaVideoConversionPreset)preset entityRenderer:(id)entityRenderer adjustments:(TGMediaVideoEditAdjustments *)adjustments inhibitAudio:(bool)inhibitAudio conversionContext:(SAtomic *)outConversionContext error:(NSError **)error { - if ([item isKindOfClass:[AVAsset class]]) { + if (image == nil) { TGMediaSampleBufferProcessor *videoProcessor = nil; TGMediaSampleBufferProcessor *audioProcessor = nil; - AVAsset *avAsset = (AVAsset *)item; - AVAssetTrack *audioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] firstObject]; AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; if (videoTrack == nil) @@ -512,7 +534,7 @@ NSDictionary *outputSettings = nil; AVMutableComposition *composition = [AVMutableComposition composition]; - AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:avAsset composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; + AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:avAsset image:nil composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; if (output == nil) return false; @@ -553,15 +575,26 @@ }]; return true; - } else if ([item isKindOfClass:[UIImage class]]) { + } else { TGMediaSampleBufferProcessor *videoProcessor = nil; CGSize dimensions = CGSizeZero; NSDictionary *outputSettings = nil; CMTimeRange timeRange = CMTimeRangeMake(CMTimeMakeWithSeconds(0.0, NSEC_PER_SEC), CMTimeMakeWithSeconds(4.0, NSEC_PER_SEC)); AVMutableComposition *composition = [AVMutableComposition composition]; - AVAssetTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; - AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:composition composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; + + AVAssetTrack *videoTrack = [[avAsset tracksWithMediaType:AVMediaTypeVideo] firstObject]; + if (videoTrack == nil) + return false; + + AVMutableCompositionTrack *mutableCompositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + [mutableCompositionVideoTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:nil]; + + AVMutableComposition *mock = [AVMutableComposition composition]; + AVMutableCompositionTrack *mockTrack = [mock addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; + [mockTrack insertTimeRange:timeRange ofTrack:videoTrack atTime:kCMTimeZero error:nil]; + + AVAssetReaderVideoCompositionOutput *output = [self setupVideoCompositionOutputWithAVAsset:mock image:image composition:composition videoTrack:videoTrack preset:preset entityRenderer:entityRenderer adjustments:adjustments timeRange:timeRange outputSettings:&outputSettings dimensions:&dimensions conversionContext:outConversionContext]; if (output == nil) return false; @@ -1176,9 +1209,8 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, { return (CGSize){ 240.0f, 240.0f }; } - default: - return (CGSize){ 640.0f, 640.0f }; + return (CGSize){ 848.0f, 848.0f }; } } @@ -1224,11 +1256,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, NSDictionary *codecSettings = @ { -#if DEBUG - AVVideoAverageBitRateKey: @([self _videoBitrateKbpsForPreset:preset] * 500), -#else AVVideoAverageBitRateKey: @([self _videoBitrateKbpsForPreset:preset] * 1000), -#endif AVVideoCleanApertureKey: videoCleanApertureSettings, AVVideoPixelAspectRatioKey: videoAspectRatioSettings }; @@ -1265,7 +1293,7 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer, return 300; default: - return 700; + return 500; } } diff --git a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m index d787723785..d135fb39c7 100644 --- a/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m +++ b/submodules/LegacyComponents/Sources/TGVideoEditAdjustments.m @@ -4,6 +4,8 @@ #import "TGPaintingData.h" +#import "PGPhotoEditorValues.h" + #import "TGPhotoPaintStickerEntity.h" #import "TGPhotoPaintTextEntity.h" @@ -129,6 +131,26 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5; return adjustments; } ++ (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values { + TGVideoEditAdjustments *adjustments = [[[self class] alloc] init]; + adjustments->_originalSize = values.originalSize; + CGRect cropRect = values.cropRect; + if (CGRectIsEmpty(cropRect)) { + cropRect = CGRectMake(0.0f, 0.0f, values.originalSize.width, values.originalSize.height); + } + adjustments->_cropRect = cropRect; + adjustments->_cropOrientation = values.cropOrientation; + adjustments->_cropLockedAspectRatio = values.cropLockedAspectRatio; + adjustments->_cropMirrored = values.cropMirrored; + adjustments->_toolValues = values.toolValues; + adjustments->_paintingData = values.paintingData; + adjustments->_sendAsGif = true; + adjustments->_preset = TGMediaVideoConversionPresetAnimation; + adjustments->_toolValues = values.toolValues; + + return adjustments; +} + - (instancetype)editAdjustmentsWithPreset:(TGMediaVideoConversionPreset)preset maxDuration:(NSTimeInterval)maxDuration { TGVideoEditAdjustments *adjustments = [[[self class] alloc] init]; diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 8eee272e26..57096e96dc 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -128,22 +128,27 @@ private final class LegacyAssetItemWrapper: NSObject { public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String?) -> [AnyHashable : Any]?) { return { anyDict, caption, entities, hash in let dict = anyDict as! NSDictionary + let stickers = (dict["stickers"] as? [TGDocumentMediaAttachment])?.compactMap { document -> FileMediaReference? in + if let sticker = stickerFromLegacyDocument(document) { + return FileMediaReference.standalone(media: sticker) + } else { + return nil + } + } ?? [] if (dict["type"] as! NSString) == "editedPhoto" || (dict["type"] as! NSString) == "capturedPhoto" { let image = dict["image"] as! UIImage let thumbnail = dict["previewImage"] as? UIImage - (dict["stickers"] as? Array).map { element in - } - - let stickers = (dict["stickers"] as? [TGDocumentMediaAttachment])?.compactMap { document -> FileMediaReference? in - if let sticker = stickerFromLegacyDocument(document) { - return FileMediaReference.standalone(media: sticker) - } else { - return nil - } - } ?? [] var result: [AnyHashable : Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + if let isAnimation = dict["isAnimation"] as? NSNumber, isAnimation.boolValue { + let url: String? = (dict["url"] as? String) ?? (dict["url"] as? URL)?.path + if let url = url { + let dimensions = image.size + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: 4.0), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: false, asAnimation: true, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + } + } else { + result["item" as NSString] = LegacyAssetItemWrapper(item: .image(data: .image(image), thumbnail: thumbnail, caption: caption, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + } return result } else if (dict["type"] as! NSString) == "cloudPhoto" { let asset = dict["asset"] as! TGMediaAsset @@ -203,13 +208,13 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? if let asset = dict["asset"] as? TGMediaAsset { var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .asset(asset), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } else if let url = (dict["url"] as? String) ?? (dict["url"] as? URL)?.absoluteString { let dimensions = (dict["dimensions"]! as AnyObject).cgSizeValue! let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } } else if (dict["type"] as! NSString) == "cameraVideo" { @@ -225,7 +230,7 @@ public func legacyAssetPickerItemGenerator() -> ((Any?, String?, [Any]?, String? let dimensions = previewImage.pixelSize() let duration = (dict["duration"]! as AnyObject).doubleValue! var result: [AnyHashable: Any] = [:] - result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: []), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) + result["item" as NSString] = LegacyAssetItemWrapper(item: .video(data: .tempFile(path: url, dimensions: dimensions, duration: duration), thumbnail: thumbnail, adjustments: dict["adjustments"] as? TGVideoEditAdjustments, caption: caption, asFile: asFile, asAnimation: false, stickers: stickers), timer: (dict["timer"] as? NSNumber)?.intValue, groupedId: (dict["groupedId"] as? NSNumber)?.int64Value) return result } } @@ -434,7 +439,7 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - } resource = VideoLibraryMediaResource(localIdentifier: asset.backingAsset.localIdentifier, conversion: asFile ? .passthrough : .compress(resourceAdjustments)) case let .tempFile(path, _, _): - if asFile || asAnimation { + if asFile || (asAnimation && !path.contains(".jpg")) { if let size = fileSize(path) { resource = LocalFileMediaResource(fileId: arc4random64(), size: size) account.postbox.mediaBox.moveResourceData(resource.id, fromTempPath: path) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 78df4321f2..a8be5baec7 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -262,8 +262,9 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender } entity.image(for: time, completion: { image in if var image = image { - let paintingScale = max(size.width, size.height) / 1920.0 - + let maxSide = max(size.width, size.height) + let paintingScale = maxSide / 1920.0 + var transform = CGAffineTransform(translationX: -image.extent.midX, y: -image.extent.midY) image = image.transformed(by: transform) diff --git a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift index 8a31c87ffb..d1896c6994 100644 --- a/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Sources/FetchVideoMediaResource.swift @@ -380,20 +380,43 @@ func fetchLocalFileVideoMediaResource(account: Account, resource: LocalFileVideo } let updatedSize = Atomic(value: 0) let entityRenderer = adjustments.flatMap { LegacyPaintEntityRenderer(account: account, adjustments: $0) } - let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in - var value = stat() - if stat(path, &value) == 0 { - if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { - var range: Range? - let _ = updatedSize.modify { updatedSize in - range = updatedSize ..< Int(value.st_size) - return Int(value.st_size) + let signal: SSignal + if filteredPath.contains(".jpg") { + if let data = try? Data(contentsOf: URL(fileURLWithPath: filteredPath), options: [.mappedRead]), let image = UIImage(data: data) { + signal = TGMediaVideoConverter.renderUIImage(image, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in + var value = stat() + if stat(path, &value) == 0 { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + var range: Range? + let _ = updatedSize.modify { updatedSize in + range = updatedSize ..< Int(value.st_size) + return Int(value.st_size) + } + //print("size = \(Int(value.st_size)), range: \(range!)") + subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) + } } - //print("size = \(Int(value.st_size)), range: \(range!)") - subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) - } + }), entityRenderer: entityRenderer)! + } else { + signal = SSignal.single(nil) } - }), entityRenderer: entityRenderer)! + } else { + signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: VideoConversionWatcher(update: { path, size in + var value = stat() + if stat(path, &value) == 0 { + if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) { + var range: Range? + let _ = updatedSize.modify { updatedSize in + range = updatedSize ..< Int(value.st_size) + return Int(value.st_size) + } + //print("size = \(Int(value.st_size)), range: \(range!)") + subscriber.putNext(.dataPart(resourceOffset: range!.lowerBound, data: data, range: range!, complete: false)) + } + } + }), entityRenderer: entityRenderer)! + } + let signalDisposable = signal.start(next: { next in if let result = next as? TGMediaVideoConversionResult { var value = stat()