From 43d5a228307541d64cf20fe9ef6d87cd68513a95 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 11 Dec 2023 14:27:53 +0400 Subject: [PATCH] Media editor improvements --- submodules/DrawingUI/BUILD | 1 + .../DrawingUI/Sources/DrawingScreen.swift | 27 ++- .../Sources/DrawingStickerEntityView.swift | 228 ++++++++++++++++++ .../Sources/DrawingTextEntityView.swift | 14 +- .../DrawingUI/Sources/DrawingUtils.swift | 56 +++++ .../Sources/ImageObjectSeparation.swift | 22 +- .../Sources/LegacyPaintStickersContext.swift | 2 + .../Drawing/DrawingStickerEntity.swift | 28 +++ .../Sources/MediaEditorComposerEntity.swift | 8 + 9 files changed, 375 insertions(+), 11 deletions(-) diff --git a/submodules/DrawingUI/BUILD b/submodules/DrawingUI/BUILD index 988ae6f3fd..108213a0bd 100644 --- a/submodules/DrawingUI/BUILD +++ b/submodules/DrawingUI/BUILD @@ -105,6 +105,7 @@ swift_library( "//submodules/ReactionSelectionNode", "//submodules/TelegramUI/Components/EntityKeyboard", "//submodules/Camera", + "//submodules/TelegramUI/Components/DustEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index bfa0b85500..72b1666ca6 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -3149,13 +3149,27 @@ public final class DrawingToolsInteraction { } #if DEBUG if isRectangleImage { - actions.append(ContextMenuAction(content: .text(title: "Cut Out", accessibilityLabel: "Cut Out"), action: { [weak self, weak entityView] in + actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Paint_CutOut, accessibilityLabel: presentationData.strings.Paint_CutOut), action: { [weak self, weak entityView] in if let self, let entityView, let entity = entityView.entity as? DrawingStickerEntity, case let .image(image, _) = entity.content { let _ = (cutoutStickerImage(from: image) - |> deliverOnMainQueue).start(next: { result in - if let result { + |> deliverOnMainQueue).start(next: { [weak entity] result in + if let result, let entity { let newEntity = DrawingStickerEntity(content: .image(result, .sticker)) - self.insertEntity(newEntity) + newEntity.referenceDrawingSize = entity.referenceDrawingSize + newEntity.scale = entity.scale + newEntity.position = entity.position + newEntity.rotation = entity.rotation + newEntity.mirrored = entity.mirrored + let newEntityView = self.entitiesView.add(newEntity) + + if let newEntityView = newEntityView as? DrawingStickerEntityView { + newEntityView.playCutoffAnimation() + } + self.entitiesView.selectEntity(newEntity, animate: false) + if let entityView = entityView as? DrawingStickerEntityView { + entityView.playDissolveAnimation() + self.entitiesView.remove(uuid: entity.uuid, animated: false) + } } }) } @@ -3198,6 +3212,11 @@ public final class DrawingToolsInteraction { self.insertEntity(DrawingStickerEntity(content: .image(image, isSticker ? .sticker : .rectangle)), scale: 2.5) } } + textEntityView.replaceWithAnimatedImage = { [weak self] data, thumbnailImage in + if let self { + self.insertEntity(DrawingStickerEntity(content: .animatedImage(data, thumbnailImage)), scale: 2.5) + } + } } else { if self.isVideo { entityView.seek(to: 0.0) diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift index 5e21b7c124..ca3658d896 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift @@ -13,6 +13,7 @@ import MediaEditor import UniversalMediaPlayer import TelegramPresentationData import TelegramUniversalVideoContent +import DustEffect private class BlurView: UIVisualEffectView { private func setup() { @@ -65,6 +66,7 @@ public class DrawingStickerEntityView: DrawingEntityView { let imageNode: TransformImageNode var animationNode: DefaultAnimatedStickerNodeImpl? var videoNode: UniversalVideoNode? + var animatedImageView: UIImageView? var cameraPreviewView: UIView? let progressDisposable = MetaDisposable() @@ -143,6 +145,8 @@ public class DrawingStickerEntityView: DrawingEntityView { return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) case let .image(image, _): return image.size + case let .animatedImage(_, thumbnailImage): + return thumbnailImage.size case let .video(file): return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) case .dualVideoReference: @@ -297,6 +301,14 @@ public class DrawingStickerEntityView: DrawingEntityView { self.videoNode = videoNode self.setNeedsLayout() videoNode.play() + } else if case let .animatedImage(data, thumbnailImage) = self.stickerEntity.content { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + imageView.image = thumbnailImage + imageView.setDrawingAnimatedImage(data: data) + self.animatedImageView = imageView + self.addSubview(imageView) + self.setNeedsLayout() } } @@ -460,6 +472,106 @@ public class DrawingStickerEntityView: DrawingEntityView { } } + public func playDissolveAnimation(completion: @escaping () -> Void = {}) { + guard let containerView = self.containerView, case let .image(image, _) = self.stickerEntity.content else { + return + } + + let dustEffectLayer = DustEffectLayer() + dustEffectLayer.position = containerView.bounds.center + dustEffectLayer.bounds = CGRect(origin: CGPoint(), size: containerView.bounds.size) + dustEffectLayer.animationSpeed = 2.2 + dustEffectLayer.becameEmpty = { [weak dustEffectLayer] in + dustEffectLayer?.removeFromSuperlayer() + completion() + } + containerView.layer.insertSublayer(dustEffectLayer, below: self.layer) + + let itemFrame = self.layer.convert(self.bounds, to: dustEffectLayer) + dustEffectLayer.addItem(frame: itemFrame, image: image) + + self.isHidden = true + } + + public func playCutoffAnimation() { + let values = [self.entity.scale, self.entity.scale * 1.1, self.entity.scale] + let keyTimes = [0.0, 0.67, 1.0] + self.layer.animateKeyframes(values: values as [NSNumber], keyTimes: keyTimes as [NSNumber], duration: 0.35, keyPath: "transform.scale") +// func blob(pointsCount: Int, randomness: CGFloat) -> [CGPoint] { +// let angle = (CGFloat.pi * 2) / CGFloat(pointsCount) +// +// let rgen = { () -> CGFloat in +// let accuracy: UInt32 = 1000 +// let random = arc4random_uniform(accuracy) +// return CGFloat(random) / CGFloat(accuracy) +// } +// let rangeStart: CGFloat = 1 / (1 + randomness / 10) +// +// let startAngle = angle * CGFloat(arc4random_uniform(100)) / CGFloat(100) +// let points = (0 ..< pointsCount).map { i -> CGPoint in +// let randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2 +// let angleRandomness: CGFloat = angle * 0.1 +// let randAngle = angle + angle * ((angleRandomness * CGFloat(arc4random_uniform(100)) / CGFloat(100)) - angleRandomness * 0.5) +// let pointX = sin(startAngle + CGFloat(i) * randAngle) +// let pointY = cos(startAngle + CGFloat(i) * randAngle) +// return CGPoint( +// x: pointX * randPointOffset, +// y: pointY * randPointOffset +// ) +// } +// return points +// } +// +// func generateNextBlob(for size: CGSize) -> [CGPoint] { +// let pointsCount = 8 +// let minRandomness = 1.0 +// let maxRandomness = 1.0 +// let speedLevel = 0.8 +// let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel +// return blob(pointsCount: pointsCount, randomness: randomness) +// .map { +// return CGPoint( +// x: $0.x * CGFloat(size.width), +// y: $0.y * CGFloat(size.height) +// ) +// } +// } +// +// guard case let .image(image, _) = self.stickerEntity.content else { +// return +// } +// let maskView = UIImageView() +// maskView.frame = self.bounds +// maskView.image = image +// self.mask = maskView +// +// let blobLayer = CAShapeLayer() +// blobLayer.strokeColor = UIColor.red.cgColor +// blobLayer.fillColor = UIColor.clear.cgColor +// blobLayer.lineWidth = 2.0 +// blobLayer.shadowRadius = 3.0 +// blobLayer.shadowOpacity = 0.8 +// blobLayer.shadowColor = UIColor.white.cgColor +// blobLayer.position = CGPoint( +// x: CGFloat.random(in: self.bounds.width * 0.33 ..< self.bounds.width * 0.5), +// y: self.bounds.height * 0.5 +// ) +// +// +// let minSide = min(self.bounds.width, self.bounds.height) +// let size = CGSize(width: minSide * 0.5, height: minSide * 0.5) +// blobLayer.bounds = CGRect(origin: .zero, size: size) +// +// let points = generateNextBlob(for: size) +// blobLayer.path = UIBezierPath.smoothCurve(through: points, length: size.width).cgPath +// self.layer.addSublayer(blobLayer) +// +// blobLayer.animateScale(from: 0.01, to: 3.0, duration: 1.0, removeOnCompletion: false, completion: { _ in +// blobLayer.removeFromSuperlayer() +// self.mask = nil +// }) + } + private var didApplyVisibility = false public override func layoutSubviews() { super.layoutSubviews() @@ -496,6 +608,10 @@ public class DrawingStickerEntityView: DrawingEntityView { videoNode.updateLayout(size: imageSize, transition: .immediate) } + if let animatedImageView = self.animatedImageView { + animatedImageView.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize) + } + if let cameraPreviewView = self.cameraPreviewView { cameraPreviewView.layer.cornerRadius = imageSize.width / 2.0 cameraPreviewView.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize) @@ -557,15 +673,18 @@ public class DrawingStickerEntityView: DrawingEntityView { self.imageNode.transform = animationSourceTransform self.animationNode?.transform = animationSourceTransform self.videoNode?.transform = animationSourceTransform + self.animatedImageView?.layer.transform = animationSourceTransform UIView.animate(withDuration: 0.25, animations: { self.imageNode.transform = animationTargetTransform self.animationNode?.transform = animationTargetTransform self.videoNode?.transform = animationTargetTransform + self.animatedImageView?.layer.transform = animationTargetTransform }, completion: { finished in self.imageNode.transform = staticTransform self.animationNode?.transform = staticTransform self.videoNode?.transform = staticTransform + self.animatedImageView?.layer.transform = staticTransform }) } else { CATransaction.begin() @@ -573,6 +692,7 @@ public class DrawingStickerEntityView: DrawingEntityView { self.imageNode.transform = staticTransform self.animationNode?.transform = staticTransform self.videoNode?.transform = staticTransform + self.animatedImageView?.layer.transform = staticTransform CATransaction.commit() } } @@ -1029,3 +1149,111 @@ private final class StickerVideoDecoration: UniversalVideoDecoration { public func tap() { } } + +private extension UIBezierPath { + static func smoothCurve( + through points: [CGPoint], + length: CGFloat + ) -> UIBezierPath { + let angle = (CGFloat.pi * 2) / CGFloat(points.count) + let smoothness: CGFloat = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2 + + var smoothPoints = [SmoothPoint]() + for index in (0 ..< points.count) { + let prevIndex = index - 1 + let prev = points[prevIndex >= 0 ? prevIndex : points.count + prevIndex] + let curr = points[index] + let next = points[(index + 1) % points.count] + + let angle: CGFloat = { + let dx = next.x - prev.x + let dy = -next.y + prev.y + let angle = atan2(dy, dx) + if angle < 0 { + return abs(angle) + } else { + return 2 * .pi - angle + } + }() + + smoothPoints.append( + SmoothPoint( + point: curr, + inAngle: angle + .pi, + inLength: smoothness * distance(from: curr, to: prev), + outAngle: angle, + outLength: smoothness * distance(from: curr, to: next) + ) + ) + } + + let resultPath = UIBezierPath() + resultPath.move(to: smoothPoints[0].point) + for index in (0 ..< smoothPoints.count) { + let curr = smoothPoints[index] + let next = smoothPoints[(index + 1) % points.count] + let currSmoothOut = curr.smoothOut() + let nextSmoothIn = next.smoothIn() + resultPath.addCurve(to: next.point, controlPoint1: currSmoothOut, controlPoint2: nextSmoothIn) + } + resultPath.close() + return resultPath + } + + static private func distance(from fromPoint: CGPoint, to toPoint: CGPoint) -> CGFloat { + return sqrt((fromPoint.x - toPoint.x) * (fromPoint.x - toPoint.x) + (fromPoint.y - toPoint.y) * (fromPoint.y - toPoint.y)) + } + + struct SmoothPoint { + let point: CGPoint + + let inAngle: CGFloat + let inLength: CGFloat + + let outAngle: CGFloat + let outLength: CGFloat + + func smoothIn() -> CGPoint { + return smooth(angle: inAngle, length: inLength) + } + + func smoothOut() -> CGPoint { + return smooth(angle: outAngle, length: outLength) + } + + private func smooth(angle: CGFloat, length: CGFloat) -> CGPoint { + return CGPoint( + x: point.x + length * cos(angle), + y: point.y + length * sin(angle) + ) + } + } +} + + + +extension UIImageView { + func setDrawingAnimatedImage(data: Data) { + DispatchQueue.global().async { + if let animatedImage = UIImage.animatedImageFromData(data: data) { + DispatchQueue.main.async { + self.setImage(with: animatedImage) + self.startAnimating() + } + } + } + } + + private func setImage(with animatedImage: DrawingAnimatedImage) { + if let snapshotView = self.snapshotView(afterScreenUpdates: false) { + self.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + } + self.image = nil + self.animationImages = animatedImage.images + self.animationDuration = animatedImage.duration + self.animationRepeatCount = 0 + } +} diff --git a/submodules/DrawingUI/Sources/DrawingTextEntityView.swift b/submodules/DrawingUI/Sources/DrawingTextEntityView.swift index c062142fa0..3351c8ae67 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntityView.swift @@ -34,6 +34,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate var textChanged: () -> Void = {} var replaceWithImage: (UIImage, Bool) -> Void = { _, _ in } + var replaceWithAnimatedImage: (Data, UIImage) -> Void = { _, _ in } init(context: AccountContext, entity: DrawingTextEntity) { self.blurredBackgroundView = BlurredBackgroundView(color: UIColor(white: 0.0, alpha: 0.25), enableBlur: true) @@ -103,8 +104,13 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate var images: [UIImage] = [] var isPNG = false var isMemoji = false + var animatedImageData: Data? for item in pasteboard.items { - if let image = item["com.apple.png-sticker"] as? UIImage { + print(item.keys) + if let data = item["public.heics"] as? Data, let image = item[kUTTypePNG as String] as? UIImage { + animatedImageData = data + images.append(image) + } else if let imageData = item["com.apple.png-sticker"] as? Data, let image = UIImage(data: imageData) { images.append(image) isPNG = true isMemoji = true @@ -121,6 +127,12 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate } } + if let animatedImageData, let image = images.first { + self.endEditing(reset: true) + self.replaceWithAnimatedImage(animatedImageData, image) + return false + } + if isPNG && images.count == 1, let image = images.first, let cgImage = image.cgImage { let maxSide = max(image.size.width, image.size.height) if maxSide.isZero { diff --git a/submodules/DrawingUI/Sources/DrawingUtils.swift b/submodules/DrawingUI/Sources/DrawingUtils.swift index 43f6047068..1050beaafb 100644 --- a/submodules/DrawingUI/Sources/DrawingUtils.swift +++ b/submodules/DrawingUI/Sources/DrawingUtils.swift @@ -544,3 +544,59 @@ extension CATransform3D { return (t, r, s) } } + +public extension UIImage { + class func animatedImageFromData(data: Data) -> DrawingAnimatedImage? { + guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { + return nil + } + + let count = CGImageSourceGetCount(source) + var images = [UIImage]() + var duration = 0.0 + + for i in 0.. Double { + var delay = 0.0 + + let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil) + let gifPropertiesPointer = UnsafeMutablePointer.allocate(capacity: 0) + if CFDictionaryGetValueIfPresent(cfProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDictionary).toOpaque(), gifPropertiesPointer) == false { + return delay + } + + let gifProperties:CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self) + + var delayObject: AnyObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyGIFUnclampedDelayTime).toOpaque()), to: AnyObject.self) + if delayObject.doubleValue == 0 { + delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyGIFDelayTime).toOpaque()), to: AnyObject.self) + } + + delay = delayObject as? Double ?? 0 + + return delay + } +} + +public final class DrawingAnimatedImage { + public let images: [UIImage] + public let duration: Double + + init(images: [UIImage], duration: Double) { + self.images = images + self.duration = duration + } +} diff --git a/submodules/DrawingUI/Sources/ImageObjectSeparation.swift b/submodules/DrawingUI/Sources/ImageObjectSeparation.swift index 5d7e8b6146..c27efa81de 100644 --- a/submodules/DrawingUI/Sources/ImageObjectSeparation.swift +++ b/submodules/DrawingUI/Sources/ImageObjectSeparation.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Vision import CoreImage +import CoreImage.CIFilterBuiltins import SwiftSignalKit import VideoToolbox @@ -13,6 +14,8 @@ func cutoutStickerImage(from image: UIImage) -> Signal { return .single(nil) } return Signal { subscriber in + let ciContext = CIContext(options: nil) + let inputImage = CIImage(cgImage: cgImage) let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) let request = VNGenerateForegroundInstanceMaskRequest { [weak handler] request, error in guard let handler, let result = request.results?.first as? VNInstanceMaskObservation else { @@ -21,13 +24,20 @@ func cutoutStickerImage(from image: UIImage) -> Signal { return } let instances = instances(atPoint: nil, inObservation: result) - if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler), let image = UIImage(pixelBuffer: mask) { - subscriber.putNext(image) - subscriber.putCompletion() - } else { - subscriber.putNext(nil) - subscriber.putCompletion() + if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler) { + let filter = CIFilter.blendWithMask() + filter.inputImage = inputImage + filter.backgroundImage = CIImage(color: .clear) + filter.maskImage = CIImage(cvPixelBuffer: mask) + if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) { + let image = UIImage(cgImage: cgImage) + subscriber.putNext(image) + subscriber.putCompletion() + return + } } + subscriber.putNext(nil) + subscriber.putCompletion() } try? handler.perform([request]) return ActionDisposable { diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 95033c420f..d79dd5b85c 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -146,6 +146,8 @@ private class LegacyPaintStickerEntity: LegacyPaintEntity { case let .image(image, _): self.file = nil self.imagePromise.set(.single(image)) + case .animatedImage: + self.file = nil case .video: self.file = nil case .dualVideoReference: diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift index 41e3fda58a..2cfe7230f7 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift @@ -29,6 +29,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { } case file(TelegramMediaFile, FileType) case image(UIImage, ImageType) + case animatedImage(Data, UIImage) case video(TelegramMediaFile) case dualVideoReference(Bool) @@ -46,6 +47,12 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { } else { return false } + case let .animatedImage(lhsData, lhsThumbnailImage): + if case let .animatedImage(rhsData, rhsThumbnailImage) = lhs { + return lhsData == rhsData && lhsThumbnailImage === rhsThumbnailImage + } else { + return false + } case let .video(lhsFile): if case let .video(rhsFile) = rhs { return lhsFile.fileId == rhsFile.fileId @@ -67,6 +74,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { case reaction case reactionStyle case imagePath + case animatedImagePath case videoFile case isRectangle case isDualPhoto @@ -112,6 +120,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { switch self.content { case let .image(image, _): dimensions = image.size + case let .animatedImage(_, thumbnailImage): + dimensions = thumbnailImage.size case let .file(file, type): if case .reaction = type { dimensions = CGSize(width: 512.0, height: 512.0) @@ -143,6 +153,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { } case .image: return false + case .animatedImage: + return true case .video: return true case .dualVideoReference: @@ -211,6 +223,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { imageType = .sticker } self.content = .image(image, imageType) + } else if let dataPath = try container.decodeIfPresent(String.self, forKey: .animatedImagePath), let data = try? Data(contentsOf: URL(fileURLWithPath: fullEntityMediaPath(dataPath))), let imagePath = try container.decodeIfPresent(String.self, forKey: .imagePath), let thumbnailImage = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) { + self.content = .animatedImage(data, thumbnailImage) } else if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .videoFile) { self.content = .video(file) } else { @@ -257,6 +271,20 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { default: break } + case let .animatedImage(data, thumbnailImage): + let dataPath = "\(self.uuid).heics" + let fullDataPath = fullEntityMediaPath(dataPath) + try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true) + try? data.write(to: URL(fileURLWithPath: fullDataPath)) + try container.encodeIfPresent(dataPath, forKey: .animatedImagePath) + + let imagePath = "\(self.uuid).png" + let fullImagePath = fullEntityMediaPath(imagePath) + if let imageData = thumbnailImage.pngData() { + try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true) + try? imageData.write(to: URL(fileURLWithPath: fullImagePath)) + try container.encodeIfPresent(imagePath, forKey: .imagePath) + } case let .video(file): try container.encode(file, forKey: .videoFile) case let .dualVideoReference(isAdditional): diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift index 397c299d5a..ae10b74f61 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift @@ -72,6 +72,9 @@ func composerEntitiesForDrawingEntity(postbox: Postbox, textScale: CGFloat, enti content = .file(file) case let .image(image, _): content = .image(image) + case let .animatedImage(data, _): + let _ = data + return [] case let .video(file): content = .video(file) case .dualVideoReference: @@ -142,6 +145,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { case file(TelegramMediaFile) case video(TelegramMediaFile) case image(UIImage) + case animatedImage([UIImage], Double) var file: TelegramMediaFile? { if case let .file(file) = self { @@ -270,6 +274,10 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { case let .image(image): self.isAnimated = false self.imagePromise.set(.single(image)) + case let .animatedImage(images, duration): + self.isAnimated = true + let _ = images + let _ = duration case .video: self.isAnimated = true }