mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2026-01-03 19:54:31 +00:00
[WIP] Stickers editor
This commit is contained in:
@@ -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)))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user