From f79bbc3ebabb2152a2be12e14607b42321f9773e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 8 Apr 2024 03:35:16 +0400 Subject: [PATCH] [WIP] Stickers editor --- .../PendingMessages/EnqueueMessage.swift | 6 +--- .../SyncCore/SyncCore_MediaReference.swift | 27 ++++++++++++++++ .../MediaEditor/Sources/MediaEditor.swift | 11 ++----- .../Sources/MediaCutoutScreen.swift | 32 ++++++++++++++----- .../Sources/MediaEditorScreen.swift | 31 ++++++++++++------ .../Sources/StickerCutoutOutlineView.swift | 15 +++++++++ .../TransformOutgoingMessageMedia.swift | 16 +++++----- 7 files changed, 98 insertions(+), 40 deletions(-) diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 2b70d10d63..7f812a1476 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -330,11 +330,7 @@ private func opportunisticallyTransformOutgoingMedia(network: Network, postbox: if let mediaReference = mediaReference { signals.append(opportunisticallyTransformMessageWithMedia(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, mediaReference: mediaReference, userInteractive: userInteractive) |> map { result -> (Bool, EnqueueMessage) in - if let result = result { - return (true, .message(text: text, attributes: attributes, inlineStickers: inlineStickers, mediaReference: .standalone(media: result.media), threadId: threadId, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, localGroupingKey: localGroupingKey, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)) - } else { - return (false, .message(text: text, attributes: attributes, inlineStickers: inlineStickers, mediaReference: mediaReference, threadId: threadId, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, localGroupingKey: localGroupingKey, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)) - } + return (result != nil, .message(text: text, attributes: attributes, inlineStickers: inlineStickers, mediaReference: result ?? mediaReference, threadId: threadId, replyToMessageId: replyToMessageId, replyToStoryId: replyToStoryId, localGroupingKey: localGroupingKey, correlationId: correlationId, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)) }) } else { signals.append(.single((false, message))) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_MediaReference.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_MediaReference.swift index 9d6213c71c..afe0961520 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_MediaReference.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_MediaReference.swift @@ -444,6 +444,33 @@ public enum AnyMediaReference: Equatable { } } + public func withUpdatedMedia(_ media: Media) -> AnyMediaReference { + switch self { + case .standalone: + return .standalone(media: media) + case let .message(message, _): + return .message(message: message, media: media) + case let .webPage(webPage, _): + return .webPage(webPage: webPage, media: media) + case let .stickerPack(stickerPack, _): + return .stickerPack(stickerPack: stickerPack, media: media) + case .savedGif: + return .savedGif(media: media) + case .savedSticker: + return .savedSticker(media: media) + case .recentSticker: + return .recentSticker(media: media) + case let .avatarList(peer, _): + return .avatarList(peer: peer, media: media) + case let .attachBot(peer, _): + return .attachBot(peer: peer, media: media) + case .customEmoji: + return .customEmoji(media: media) + case let .story(peer, id, _): + return .story(peer: peer, id: id, media: media) + } + } + public func resourceReference(_ resource: MediaResource) -> MediaResourceReference { return .media(media: self, resource: resource) } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 13f4cc8701..2a94a06a80 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -194,7 +194,6 @@ public final class MediaEditor { public private(set) var canCutout: Bool = false public var canCutoutUpdated: (Bool, Bool) -> Void = { _, _ in } - public var isCutoutUpdated: (Bool) -> Void = { _ in } public var maskUpdated: (UIImage) -> Void = { _ in } public var classificationUpdated: ([(String, Float)]) -> Void = { _ in } @@ -1721,9 +1720,7 @@ public final class MediaEditor { } private var mainInputMask: MTLTexture? - public func removeSegmentationMask() { - self.isCutoutUpdated(false) - + public func removeSegmentationMask() { self.mainInputMask = nil self.renderer.currentMainInputMask = nil if !self.skipRendering { @@ -1731,15 +1728,11 @@ public final class MediaEditor { } } - public func setSegmentationMask(_ image: UIImage, andEnable enable: Bool = false, updateCutout: Bool = false) { + public func setSegmentationMask(_ image: UIImage, andEnable enable: Bool = false) { guard let renderTarget = self.previewView, let device = renderTarget.mtlDevice else { return } - if updateCutout { - self.isCutoutUpdated(true) - } - //TODO:replace with pixelbuffer? self.mainInputMask = loadTexture(image: image, device: device) if enable { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift index f30ac1668d..838538984c 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift @@ -102,14 +102,15 @@ private final class MediaCutoutScreenComponent: Component { } @objc private func previewTap(_ gestureRecognizer: UITapGestureRecognizer) { - guard let component = self.component else { + guard let component = self.component, let controller = self.environment?.controller() as? MediaCutoutScreen else { return } - let location = gestureRecognizer.location(in: gestureRecognizer.view) + + let location = gestureRecognizer.location(in: controller.drawingView) let point = CGPoint( - x: location.x / self.previewContainerView.frame.width, - y: location.y / self.previewContainerView.frame.height + x: location.x / controller.drawingView.bounds.width, + y: location.y / controller.drawingView.bounds.height ) component.mediaEditor.processImage { [weak self] originalImage, _ in @@ -118,7 +119,7 @@ private final class MediaCutoutScreenComponent: Component { if let self, let _ = self.component, let result = results.first, let maskImage = result.maskImage, let controller = self.environment?.controller() as? MediaCutoutScreen { if case let .image(mask, _) = maskImage { self.playDissolveAnimation() - component.mediaEditor.setSegmentationMask(mask, updateCutout: true) + component.mediaEditor.setSegmentationMask(mask) if let maskData = mask.pngData() { controller.drawingView.setup(withDrawing: maskData) } @@ -189,7 +190,7 @@ private final class MediaCutoutScreenComponent: Component { let mediaEditor = controller.mediaEditor if let drawingImage = controller.drawingView.drawingImage { - mediaEditor.setSegmentationMask(drawingImage, andEnable: true, updateCutout: false) + mediaEditor.setSegmentationMask(drawingImage, andEnable: true) } let initialOutlineValue = self.initialOutlineValue mediaEditor.setOnNextDisplay { [weak controller, weak mediaEditor] in @@ -197,6 +198,7 @@ private final class MediaCutoutScreenComponent: Component { if let initialOutlineValue { mediaEditor?.setToolValue(.stickerOutline, value: initialOutlineValue) } + controller?.completed() } self.animatingOut = true @@ -249,6 +251,7 @@ private final class MediaCutoutScreenComponent: Component { dustEffectLayer.addItem(frame: previewView.bounds, image: resultImage) + controller.completedWithCutout() controller.requestDismiss(animated: true) } @@ -386,7 +389,19 @@ private final class MediaCutoutScreenComponent: Component { if labelView.superview == nil { self.buttonsContainerView.addSubview(labelView) } - transition.setFrame(view: labelView, frame: labelFrame) + if labelView.bounds.width > 0.0 && labelFrame.width != labelView.bounds.width { + if let snapshotView = labelView.snapshotView(afterScreenUpdates: false) { + snapshotView.center = labelView.center + self.buttonsContainerView.addSubview(snapshotView) + + labelView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + } + } + labelView.bounds = CGRect(origin: .zero, size: labelFrame.size) + transition.setPosition(view: labelView, position: labelFrame.center) } transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame) @@ -403,7 +418,6 @@ private final class MediaCutoutScreenComponent: Component { self.fadeView.frame = CGRect(x: floorToScreenPixels((previewContainerFrame.width - frameWidth) / 2.0), y: previewContainerFrame.minY + floorToScreenPixels((previewContainerFrame.height - frameWidth) / 2.0), width: frameWidth, height: frameWidth) self.fadeView.layer.cornerRadius = frameWidth / 8.0 - if isFirstTime { let values = component.mediaEditor.values component.mediaEditor.processImage { originalImage, editedImage in @@ -598,6 +612,8 @@ final class MediaCutoutScreen: ViewController { fileprivate let overlayView: UIView fileprivate let backgroundView: UIView + var completed: () -> Void = {} + var completedWithCutout: () -> Void = {} var dismissed: () -> Void = {} private var initialValues: MediaEditorValues diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index b6bc653571..cf097e1d81 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1132,10 +1132,14 @@ final class MediaEditorScreenComponent: Component { } else { if let cutoutButtonView = self.cutoutButton.view, cutoutButtonView.superview != nil { cutoutButtonView.alpha = 0.0 - cutoutButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.0, completion: { _ in + if transition.animation.isImmediate { cutoutButtonView.removeFromSuperview() - }) - cutoutButtonView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, delay: 0.0) + } else { + cutoutButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.0, completion: { _ in + cutoutButtonView.removeFromSuperview() + }) + cutoutButtonView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, delay: 0.0) + } } } @@ -2927,13 +2931,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.hasTransparency = hasTransparency self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25)) } - mediaEditor.isCutoutUpdated = { [weak self] isCutout in - guard let self else { - return - } - self.isCutout = isCutout - self.requestLayout(forceUpdate: true, transition: .immediate) - } mediaEditor.maskUpdated = { [weak self] mask in guard let self else { return @@ -4736,6 +4733,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate overlayView: self.stickerMaskPreviewView, backgroundView: stickerBackgroundView ) + cutoutController.completedWithCutout = { [weak self] in + if let self { + self.isCutout = true + self.requestLayout(forceUpdate: true, transition: .immediate) + } + } + cutoutController.completed = { [weak self] in + if let self { + self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25)) + } + } cutoutController.dismissed = { [weak self] in if let self { self.animateInFromTool(inPlace: true) @@ -4802,6 +4810,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate }) mediaEditor.removeSegmentationMask() self.stickerMaskDrawingView?.clearWithEmptyColor() + + self.isCutout = false + self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25)) } if let value = mediaEditor.getToolValue(.stickerOutline) as? Float, value > 0.0 { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift index 4a16dc3db1..5e001b4105 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/StickerCutoutOutlineView.swift @@ -169,6 +169,21 @@ final class StickerCutoutOutlineView: UIView { self.outline2Layer.animateAlpha(from: 0.0, to: CGFloat(self.outline2Layer.opacity), duration: 0.4, delay: 0.0) self.glowLayer.animateAlpha(from: 0.0, to: CGFloat(self.glowLayer.opacity), duration: 0.4, delay: 0.0) + + self.animateBump(path: path) + } + + private func animateBump(path: BezierPath) { + let boundingBox = path.path.cgPath.boundingBox + let pathCenter = CGPoint(x: boundingBox.midX, y: boundingBox.midY) + +// let originalPosition = self.imageLayer.position +// let originalAnchorPoint = self.imageLayer.anchorPoint + + let layerPathCenter = self.imageLayer.convert(pathCenter, from: self.imageLayer.superlayer) + self.imageLayer.anchorPoint = CGPoint(x: layerPathCenter.x / layer.bounds.width, y: layerPathCenter.y / layer.bounds.height) + self.imageLayer.position = layerPathCenter + let values = [1.0, 1.07, 1.0] let keyTimes = [0.0, 0.67, 1.0] self.imageLayer.animateKeyframes(values: values as [NSNumber], keyTimes: keyTimes as [NSNumber], duration: 0.4, keyPath: "transform.scale", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) diff --git a/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift b/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift index 2d0679a638..9bf6587903 100644 --- a/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift +++ b/submodules/TelegramUI/Sources/TransformOutgoingMessageMedia.swift @@ -68,21 +68,21 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me } attributes.append(.ImageSize(size: PixelDimensions(imageDimensions))) let updatedFile = file.withUpdatedSize(data.size).withUpdatedPreviewRepresentations([TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledImageSize), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)]).withUpdatedAttributes(attributes) - subscriber.putNext(.standalone(media: updatedFile)) + subscriber.putNext(media.withUpdatedMedia(updatedFile)) subscriber.putCompletion() } else { let updatedFile = file.withUpdatedSize(data.size) - subscriber.putNext(.standalone(media: updatedFile)) + subscriber.putNext(media.withUpdatedMedia(updatedFile)) subscriber.putCompletion() } } else { let updatedFile = file.withUpdatedSize(data.size) - subscriber.putNext(.standalone(media: updatedFile)) + subscriber.putNext(media.withUpdatedMedia(updatedFile)) subscriber.putCompletion() } } else { let updatedFile = file.withUpdatedSize(data.size) - subscriber.putNext(.standalone(media: updatedFile)) + subscriber.putNext(media.withUpdatedMedia(updatedFile)) subscriber.putCompletion() } @@ -97,11 +97,11 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me let scaledImageSize = CGSize(width: scaledImage.size.width * scaledImage.scale, height: scaledImage.size.height * scaledImage.scale) let updatedFile = file.withUpdatedSize(data.size).withUpdatedPreviewRepresentations([TelegramMediaImageRepresentation(dimensions: PixelDimensions(scaledImageSize), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)]) - subscriber.putNext(.standalone(media: updatedFile)) + subscriber.putNext(media.withUpdatedMedia(updatedFile)) subscriber.putCompletion() } else { let updatedFile = file.withUpdatedSize(data.size) - subscriber.putNext(.standalone(media: updatedFile)) + subscriber.putNext(media.withUpdatedMedia(updatedFile)) subscriber.putCompletion() } @@ -109,7 +109,7 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me } |> runOn(opportunistic ? Queue.mainQueue() : Queue.concurrentDefaultQueue()) } else { let updatedFile = file.withUpdatedSize(data.size) - return .single(.standalone(media: updatedFile)) + return .single(media.withUpdatedMedia(updatedFile)) } } else if opportunistic { return .single(nil) @@ -158,7 +158,7 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me postbox.mediaBox.storeResourceData(thumbnailResource.id, data: smallestData) representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(smallestSize), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) let updatedImage = TelegramMediaImage(imageId: image.imageId, representations: representations, immediateThumbnailData: image.immediateThumbnailData, reference: image.reference, partialReference: image.partialReference, flags: []) - return .single(.standalone(media: updatedImage)) + return .single(media.withUpdatedMedia(updatedImage)) } }