Camera and editor improvements

This commit is contained in:
Ilya Laktyushin 2023-05-15 15:12:51 +04:00
parent 37e7a74e4e
commit a2d7cfba4f
39 changed files with 1877 additions and 1338 deletions

View File

@ -13,6 +13,7 @@ import BlurredBackgroundComponent
import SegmentedControlNode import SegmentedControlNode
import MultilineTextComponent import MultilineTextComponent
import HexColor import HexColor
import MediaEditor
private let palleteColors: [UInt32] = [ private let palleteColors: [UInt32] = [
0xffffff, 0xebebeb, 0xd6d6d6, 0xc2c2c2, 0xadadad, 0x999999, 0x858585, 0x707070, 0x5c5c5c, 0x474747, 0x333333, 0x000000, 0xffffff, 0xebebeb, 0xd6d6d6, 0xc2c2c2, 0xadadad, 0x999999, 0x858585, 0x707070, 0x5c5c5c, 0x474747, 0x333333, 0x000000,

View File

@ -2,119 +2,7 @@ import Foundation
import UIKit import UIKit
import Display import Display
import AccountContext import AccountContext
import MediaEditor
public final class DrawingBubbleEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case drawType
case color
case lineWidth
case referenceDrawingSize
case position
case size
case rotation
case tailPosition
case renderImage
}
enum DrawType: Codable {
case fill
case stroke
}
public let uuid: UUID
public let isAnimated: Bool
var drawType: DrawType
public var color: DrawingColor
public var lineWidth: CGFloat
var referenceDrawingSize: CGSize
public var position: CGPoint
public var size: CGSize
public var rotation: CGFloat
var tailPosition: CGPoint
public var center: CGPoint {
return self.position
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var isMedia: Bool {
return false
}
init(drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.drawType = drawType
self.color = color
self.lineWidth = lineWidth
self.referenceDrawingSize = .zero
self.position = .zero
self.size = CGSize(width: 1.0, height: 1.0)
self.rotation = 0.0
self.tailPosition = CGPoint(x: 0.16, y: 0.18)
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.drawType = try container.decode(DrawType.self, forKey: .drawType)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.size = try container.decode(CGSize.self, forKey: .size)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.tailPosition = try container.decode(CGPoint.self, forKey: .tailPosition)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.drawType, forKey: .drawType)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.size, forKey: .size)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.tailPosition, forKey: .tailPosition)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingBubbleEntity(drawType: self.drawType, color: self.color, lineWidth: self.lineWidth)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.size = self.size
newEntity.rotation = self.rotation
return newEntity
}
public weak var currentEntityView: DrawingEntityView?
public func makeView(context: AccountContext) -> DrawingEntityView {
let entityView = DrawingBubbleEntityView(context: context, entity: self)
self.currentEntityView = entityView
return entityView
}
public func prepareForRender() {
self.renderImage = (self.currentEntityView as? DrawingBubbleEntityView)?.getRenderImage()
}
}
final class DrawingBubbleEntityView: DrawingEntityView { final class DrawingBubbleEntityView: DrawingEntityView {
private var bubbleEntity: DrawingBubbleEntity { private var bubbleEntity: DrawingBubbleEntity {

View File

@ -3,66 +3,7 @@ import UIKit
import Display import Display
import LegacyComponents import LegacyComponents
import AccountContext import AccountContext
import MediaEditor
public protocol DrawingEntity: AnyObject {
var uuid: UUID { get }
var isAnimated: Bool { get }
var center: CGPoint { get }
var isMedia: Bool { get }
var lineWidth: CGFloat { get set }
var color: DrawingColor { get set }
var scale: CGFloat { get set }
func duplicate() -> DrawingEntity
var currentEntityView: DrawingEntityView? { get }
func makeView(context: AccountContext) -> DrawingEntityView
func prepareForRender()
}
enum CodableDrawingEntity {
case sticker(DrawingStickerEntity)
case text(DrawingTextEntity)
case simpleShape(DrawingSimpleShapeEntity)
case bubble(DrawingBubbleEntity)
case vector(DrawingVectorEntity)
init?(entity: DrawingEntity) {
if let entity = entity as? DrawingStickerEntity {
self = .sticker(entity)
} else if let entity = entity as? DrawingTextEntity {
self = .text(entity)
} else if let entity = entity as? DrawingSimpleShapeEntity {
self = .simpleShape(entity)
} else if let entity = entity as? DrawingBubbleEntity {
self = .bubble(entity)
} else if let entity = entity as? DrawingVectorEntity {
self = .vector(entity)
} else {
return nil
}
}
var entity: DrawingEntity {
switch self {
case let .sticker(entity):
return entity
case let .text(entity):
return entity
case let .simpleShape(entity):
return entity
case let .bubble(entity):
return entity
case let .vector(entity):
return entity
}
}
}
public func decodeDrawingEntities(data: Data) -> [DrawingEntity] { public func decodeDrawingEntities(data: Data) -> [DrawingEntity] {
if let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: data) { if let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: data) {
@ -71,56 +12,37 @@ public func decodeDrawingEntities(data: Data) -> [DrawingEntity] {
return [] return []
} }
extension CodableDrawingEntity: Codable { private func makeEntityView(context: AccountContext, entity: DrawingEntity) -> DrawingEntityView? {
private enum CodingKeys: String, CodingKey { if let entity = entity as? DrawingBubbleEntity {
case type return DrawingBubbleEntityView(context: context, entity: entity)
case entity } else if let entity = entity as? DrawingSimpleShapeEntity {
return DrawingSimpleShapeEntityView(context: context, entity: entity)
} else if let entity = entity as? DrawingStickerEntity {
return DrawingStickerEntityView(context: context, entity: entity)
} else if let entity = entity as? DrawingTextEntity {
return DrawingTextEntityView(context: context, entity: entity)
} else if let entity = entity as? DrawingVectorEntity {
return DrawingVectorEntityView(context: context, entity: entity)
} else if let entity = entity as? DrawingMediaEntity {
return DrawingMediaEntityView(context: context, entity: entity)
} else {
return nil
} }
}
private enum EntityType: Int, Codable { private func prepareForRendering(entityView: DrawingEntityView) {
case sticker if let entityView = entityView as? DrawingBubbleEntityView {
case text entityView.entity.renderImage = entityView.getRenderImage()
case simpleShape
case bubble
case vector
} }
if let entityView = entityView as? DrawingSimpleShapeEntityView {
init(from decoder: Decoder) throws { entityView.entity.renderImage = entityView.getRenderImage()
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(EntityType.self, forKey: .type)
switch type {
case .sticker:
self = .sticker(try container.decode(DrawingStickerEntity.self, forKey: .entity))
case .text:
self = .text(try container.decode(DrawingTextEntity.self, forKey: .entity))
case .simpleShape:
self = .simpleShape(try container.decode(DrawingSimpleShapeEntity.self, forKey: .entity))
case .bubble:
self = .bubble(try container.decode(DrawingBubbleEntity.self, forKey: .entity))
case .vector:
self = .vector(try container.decode(DrawingVectorEntity.self, forKey: .entity))
}
} }
if let entityView = entityView as? DrawingTextEntityView {
func encode(to encoder: Encoder) throws { entityView.entity.renderImage = entityView.getRenderImage()
var container = encoder.container(keyedBy: CodingKeys.self) entityView.entity.renderSubEntities = entityView.getRenderSubEntities()
switch self { }
case let .sticker(payload): if let entityView = entityView as? DrawingVectorEntityView {
try container.encode(EntityType.sticker, forKey: .type) entityView.entity.renderImage = entityView.getRenderImage()
try container.encode(payload, forKey: .entity)
case let .text(payload):
try container.encode(EntityType.text, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .simpleShape(payload):
try container.encode(EntityType.simpleShape, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .bubble(payload):
try container.encode(EntityType.bubble, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .vector(payload):
try container.encode(EntityType.vector, forKey: .type)
try container.encode(payload, forKey: .entity)
}
} }
} }
@ -227,13 +149,17 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
} }
} }
public static func encodeEntities(_ entities: [DrawingEntity]) -> Data? { public static func encodeEntities(_ entities: [DrawingEntity], entitiesView: DrawingEntitiesView? = nil) -> Data? {
let entities = entities let entities = entities
guard !entities.isEmpty else { guard !entities.isEmpty else {
return nil return nil
} }
for entity in entities { if let entitiesView {
entity.prepareForRender() for entity in entities {
if let entityView = entitiesView.getView(for: entity.uuid) {
prepareForRendering(entityView: entityView)
}
}
} }
let codableEntities = entities.compactMap({ CodableDrawingEntity(entity: $0) }) let codableEntities = entities.compactMap({ CodableDrawingEntity(entity: $0) })
if let data = try? JSONEncoder().encode(codableEntities) { if let data = try? JSONEncoder().encode(codableEntities) {
@ -244,7 +170,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
} }
var entitiesData: Data? { var entitiesData: Data? {
return DrawingEntitiesView.encodeEntities(self.entities) return DrawingEntitiesView.encodeEntities(self.entities, entitiesView: self)
} }
var hasChanges: Bool { var hasChanges: Bool {
@ -351,7 +277,9 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
@discardableResult @discardableResult
public func add(_ entity: DrawingEntity, announce: Bool = true) -> DrawingEntityView { public func add(_ entity: DrawingEntity, announce: Bool = true) -> DrawingEntityView {
let view = entity.makeView(context: self.context) guard let view = makeEntityView(context: self.context, entity: entity) else {
fatalError()
}
view.containerView = self view.containerView = self
view.onSnapToXAxis = { [weak self, weak view] snappedToX in view.onSnapToXAxis = { [weak self, weak view] snappedToX in
@ -420,7 +348,9 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
let newEntity = entity.duplicate() let newEntity = entity.duplicate()
self.prepareNewEntity(newEntity, setup: false, relativeTo: entity) self.prepareNewEntity(newEntity, setup: false, relativeTo: entity)
let view = newEntity.makeView(context: self.context) guard let view = makeEntityView(context: self.context, entity: entity) else {
fatalError()
}
view.containerView = self view.containerView = self
view.update() view.update()
self.addSubview(view) self.addSubview(view)

View File

@ -7,152 +7,6 @@ import AccountContext
import MediaEditor import MediaEditor
import Photos import Photos
public final class DrawingMediaEntity: DrawingEntity, Codable {
public enum Content {
case image(UIImage, PixelDimensions)
case video(String, PixelDimensions)
case asset(PHAsset)
var dimensions: PixelDimensions {
switch self {
case let .image(_, dimensions), let .video(_, dimensions):
return dimensions
case let .asset(asset):
return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight))
}
}
}
private enum CodingKeys: String, CodingKey {
case uuid
case image
case videoPath
case assetId
case size
case width
case height
case referenceDrawingSize
case position
case scale
case rotation
case mirrored
}
public let uuid: UUID
public let content: Content
public let size: CGSize
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var scale: CGFloat
public var rotation: CGFloat
public var mirrored: Bool
public var color: DrawingColor = DrawingColor.clear
public var lineWidth: CGFloat = 0.0
public var center: CGPoint {
return self.position
}
public var baseSize: CGSize {
return self.size
}
public var isAnimated: Bool {
switch self.content {
case .image:
return false
case .video:
return true
case let .asset(asset):
return asset.mediaType == .video
}
}
public var isMedia: Bool {
return true
}
public init(content: Content, size: CGSize) {
self.uuid = UUID()
self.content = content
self.size = size
self.referenceDrawingSize = .zero
self.position = CGPoint()
self.scale = 1.0
self.rotation = 0.0
self.mirrored = false
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.size = try container.decode(CGSize.self, forKey: .size)
let width = try container.decode(Int32.self, forKey: .width)
let height = try container.decode(Int32.self, forKey: .height)
if let videoPath = try container.decodeIfPresent(String.self, forKey: .videoPath) {
self.content = .video(videoPath, PixelDimensions(width: width, height: height))
} else if let imageData = try container.decodeIfPresent(Data.self, forKey: .image), let image = UIImage(data: imageData) {
self.content = .image(image, PixelDimensions(width: width, height: height))
} else if let _ = try container.decodeIfPresent(String.self, forKey: .assetId) {
fatalError()
//self.content = .asset()
} else {
fatalError()
}
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.mirrored = try container.decode(Bool.self, forKey: .mirrored)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
switch self.content {
case let .video(videoPath, dimensions):
try container.encode(videoPath, forKey: .videoPath)
try container.encode(dimensions.width, forKey: .width)
try container.encode(dimensions.height, forKey: .height)
case let .image(image, dimensions):
try container.encodeIfPresent(image.jpegData(compressionQuality: 0.9), forKey: .image)
try container.encode(dimensions.width, forKey: .width)
try container.encode(dimensions.height, forKey: .height)
case let .asset(asset):
try container.encode(asset.localIdentifier, forKey: .assetId)
}
try container.encode(self.size, forKey: .size)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.mirrored, forKey: .mirrored)
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingMediaEntity(content: self.content, size: self.size)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.scale = self.scale
newEntity.rotation = self.rotation
newEntity.mirrored = self.mirrored
return newEntity
}
public weak var currentEntityView: DrawingEntityView?
public func makeView(context: AccountContext) -> DrawingEntityView {
let entityView = DrawingMediaEntityView(context: context, entity: self)
self.currentEntityView = entityView
return entityView
}
public func prepareForRender() {
}
}
public final class DrawingMediaEntityView: DrawingEntityView, DrawingEntityMediaView { public final class DrawingMediaEntityView: DrawingEntityView, DrawingEntityMediaView {
private var mediaEntity: DrawingMediaEntity { private var mediaEntity: DrawingMediaEntity {
return self.entity as! DrawingMediaEntity return self.entity as! DrawingMediaEntity

View File

@ -5,6 +5,7 @@ import MetalKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import AppBundle import AppBundle
import MediaEditor
final class DrawingMetalView: MTKView { final class DrawingMetalView: MTKView {
let size: CGSize let size: CGSize

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import UIKit import UIKit
import Display import Display
import MediaEditor
final class NeonTool: DrawingElement { final class NeonTool: DrawingElement {
class RenderView: UIView, DrawingRenderView { class RenderView: UIView, DrawingRenderView {

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import UIKit import UIKit
import Display import Display
import MediaEditor
private let activeWidthFactor: CGFloat = 0.7 private let activeWidthFactor: CGFloat = 0.7

View File

@ -21,6 +21,13 @@ import ChatEntityKeyboardInputNode
import EntityKeyboard import EntityKeyboard
import TelegramUIPreferences import TelegramUIPreferences
import FastBlur import FastBlur
import MediaEditor
public struct DrawingResultData {
public let data: Data?
public let drawingImage: UIImage?
public let entities: [CodableDrawingEntity]
}
enum DrawingToolState: Equatable, Codable { enum DrawingToolState: Equatable, Codable {
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
@ -502,6 +509,8 @@ private final class DrawingScreenComponent: CombinedComponent {
let toggleWithPreviousTool: ActionSlot<Void> let toggleWithPreviousTool: ActionSlot<Void>
let insertSticker: ActionSlot<Void> let insertSticker: ActionSlot<Void>
let insertText: ActionSlot<Void> let insertText: ActionSlot<Void>
let updateEntityView: ActionSlot<(UUID, Bool)>
let endEditingTextEntityView: ActionSlot<(UUID, Bool)>
let apply: ActionSlot<Void> let apply: ActionSlot<Void>
let dismiss: ActionSlot<Void> let dismiss: ActionSlot<Void>
@ -533,6 +542,8 @@ private final class DrawingScreenComponent: CombinedComponent {
toggleWithPreviousTool: ActionSlot<Void>, toggleWithPreviousTool: ActionSlot<Void>,
insertSticker: ActionSlot<Void>, insertSticker: ActionSlot<Void>,
insertText: ActionSlot<Void>, insertText: ActionSlot<Void>,
updateEntityView: ActionSlot<(UUID, Bool)>,
endEditingTextEntityView: ActionSlot<(UUID, Bool)>,
apply: ActionSlot<Void>, apply: ActionSlot<Void>,
dismiss: ActionSlot<Void>, dismiss: ActionSlot<Void>,
presentColorPicker: @escaping (DrawingColor) -> Void, presentColorPicker: @escaping (DrawingColor) -> Void,
@ -562,6 +573,8 @@ private final class DrawingScreenComponent: CombinedComponent {
self.toggleWithPreviousTool = toggleWithPreviousTool self.toggleWithPreviousTool = toggleWithPreviousTool
self.insertSticker = insertSticker self.insertSticker = insertSticker
self.insertText = insertText self.insertText = insertText
self.updateEntityView = updateEntityView
self.endEditingTextEntityView = endEditingTextEntityView
self.apply = apply self.apply = apply
self.dismiss = dismiss self.dismiss = dismiss
self.presentColorPicker = presentColorPicker self.presentColorPicker = presentColorPicker
@ -637,6 +650,8 @@ private final class DrawingScreenComponent: CombinedComponent {
private let toggleWithPreviousTool: ActionSlot<Void> private let toggleWithPreviousTool: ActionSlot<Void>
private let insertSticker: ActionSlot<Void> private let insertSticker: ActionSlot<Void>
private let insertText: ActionSlot<Void> private let insertText: ActionSlot<Void>
private let updateEntityView: ActionSlot<(UUID, Bool)>
private let endEditingTextEntityView: ActionSlot<(UUID, Bool)>
private let present: (ViewController) -> Void private let present: (ViewController) -> Void
var currentMode: Mode var currentMode: Mode
@ -661,6 +676,8 @@ private final class DrawingScreenComponent: CombinedComponent {
toggleWithPreviousTool: ActionSlot<Void>, toggleWithPreviousTool: ActionSlot<Void>,
insertSticker: ActionSlot<Void>, insertSticker: ActionSlot<Void>,
insertText: ActionSlot<Void>, insertText: ActionSlot<Void>,
updateEntityView: ActionSlot<(UUID, Bool)>,
endEditingTextEntityView: ActionSlot<(UUID, Bool)>,
present: @escaping (ViewController) -> Void) present: @escaping (ViewController) -> Void)
{ {
self.context = context self.context = context
@ -673,6 +690,8 @@ private final class DrawingScreenComponent: CombinedComponent {
self.toggleWithPreviousTool = toggleWithPreviousTool self.toggleWithPreviousTool = toggleWithPreviousTool
self.insertSticker = insertSticker self.insertSticker = insertSticker
self.insertText = insertText self.insertText = insertText
self.updateEntityView = updateEntityView
self.endEditingTextEntityView = endEditingTextEntityView
self.present = present self.present = present
self.currentMode = .drawing self.currentMode = .drawing
@ -808,7 +827,7 @@ private final class DrawingScreenComponent: CombinedComponent {
self.currentColor = color self.currentColor = color
if let selectedEntity = self.selectedEntity { if let selectedEntity = self.selectedEntity {
selectedEntity.color = color selectedEntity.color = color
selectedEntity.currentEntityView?.update() self.updateEntityView.invoke((selectedEntity.uuid, false))
} else { } else {
self.drawingState = self.drawingState.withUpdatedColor(color) self.drawingState = self.drawingState.withUpdatedColor(color)
self.updateToolState.invoke(self.drawingState.currentToolState) self.updateToolState.invoke(self.drawingState.currentToolState)
@ -850,7 +869,7 @@ private final class DrawingScreenComponent: CombinedComponent {
} else { } else {
selectedEntity.lineWidth = size selectedEntity.lineWidth = size
} }
selectedEntity.currentEntityView?.update() self.updateEntityView.invoke((selectedEntity.uuid, false))
} else { } else {
self.drawingState = self.drawingState.withUpdatedSize(size) self.drawingState = self.drawingState.withUpdatedSize(size)
self.updateToolState.invoke(self.drawingState.currentToolState) self.updateToolState.invoke(self.drawingState.currentToolState)
@ -996,7 +1015,7 @@ private final class DrawingScreenComponent: CombinedComponent {
} }
func makeState() -> State { func makeState() -> State {
return State(context: self.context, existingStickerPickerInputData: self.existingStickerPickerInputData, updateToolState: self.updateToolState, insertEntity: self.insertEntity, deselectEntity: self.deselectEntity, updateEntitiesPlayback: self.updateEntitiesPlayback, dismissEyedropper: self.dismissEyedropper, toggleWithEraser: self.toggleWithEraser, toggleWithPreviousTool: self.toggleWithPreviousTool, insertSticker: self.insertSticker, insertText: self.insertText, present: self.present) return State(context: self.context, existingStickerPickerInputData: self.existingStickerPickerInputData, updateToolState: self.updateToolState, insertEntity: self.insertEntity, deselectEntity: self.deselectEntity, updateEntitiesPlayback: self.updateEntitiesPlayback, dismissEyedropper: self.dismissEyedropper, toggleWithEraser: self.toggleWithEraser, toggleWithPreviousTool: self.toggleWithPreviousTool, insertSticker: self.insertSticker, insertText: self.insertText, updateEntityView: self.updateEntityView, endEditingTextEntityView: self.endEditingTextEntityView, present: self.present)
} }
static var body: Body { static var body: Body {
@ -1070,6 +1089,9 @@ private final class DrawingScreenComponent: CombinedComponent {
let dismissFastColorPicker = component.dismissFastColorPicker let dismissFastColorPicker = component.dismissFastColorPicker
let presentFontPicker = component.presentFontPicker let presentFontPicker = component.presentFontPicker
let updateEntityView = component.updateEntityView
let endEditingTextEntityView = component.endEditingTextEntityView
component.updateState.connect { [weak state] updatedState in component.updateState.connect { [weak state] updatedState in
state?.updateDrawingState(updatedState) state?.updateDrawingState(updatedState)
} }
@ -1162,9 +1184,7 @@ private final class DrawingScreenComponent: CombinedComponent {
nextStyle = .regular nextStyle = .regular
} }
textEntity.style = nextStyle textEntity.style = nextStyle
if let entityView = textEntity.currentEntityView { updateEntityView.invoke((textEntity.uuid, false))
entityView.update()
}
state?.updated(transition: .easeInOut(duration: 0.2)) state?.updated(transition: .easeInOut(duration: 0.2))
}, },
toggleAnimation: { [weak state, weak textEntity] in toggleAnimation: { [weak state, weak textEntity] in
@ -1183,9 +1203,7 @@ private final class DrawingScreenComponent: CombinedComponent {
nextAnimation = .none nextAnimation = .none
} }
textEntity.animation = nextAnimation textEntity.animation = nextAnimation
if let entityView = textEntity.currentEntityView { updateEntityView.invoke((textEntity.uuid, false))
entityView.update()
}
state?.updated(transition: .easeInOut(duration: 0.2)) state?.updated(transition: .easeInOut(duration: 0.2))
}, },
toggleAlignment: { [weak state, weak textEntity] in toggleAlignment: { [weak state, weak textEntity] in
@ -1202,9 +1220,7 @@ private final class DrawingScreenComponent: CombinedComponent {
nextAlignment = .left nextAlignment = .left
} }
textEntity.alignment = nextAlignment textEntity.alignment = nextAlignment
if let entityView = textEntity.currentEntityView { updateEntityView.invoke((textEntity.uuid, false))
entityView.update()
}
state?.updated(transition: .easeInOut(duration: 0.2)) state?.updated(transition: .easeInOut(duration: 0.2))
}, },
presentFontPicker: { presentFontPicker: {
@ -1549,14 +1565,14 @@ private final class DrawingScreenComponent: CombinedComponent {
} else { } else {
entity.drawType = .fill entity.drawType = .fill
} }
entity.currentEntityView?.update() updateEntityView.invoke((entity.uuid, false))
} else if let entity = state.selectedEntity as? DrawingBubbleEntity { } else if let entity = state.selectedEntity as? DrawingBubbleEntity {
if case .fill = entity.drawType { if case .fill = entity.drawType {
entity.drawType = .stroke entity.drawType = .stroke
} else { } else {
entity.drawType = .fill entity.drawType = .fill
} }
entity.currentEntityView?.update() updateEntityView.invoke((entity.uuid, false))
} else if let entity = state.selectedEntity as? DrawingVectorEntity { } else if let entity = state.selectedEntity as? DrawingVectorEntity {
if case .oneSidedArrow = entity.type { if case .oneSidedArrow = entity.type {
entity.type = .twoSidedArrow entity.type = .twoSidedArrow
@ -1565,7 +1581,7 @@ private final class DrawingScreenComponent: CombinedComponent {
} else { } else {
entity.type = .oneSidedArrow entity.type = .oneSidedArrow
} }
entity.currentEntityView?.update() updateEntityView.invoke((entity.uuid, false))
} }
state.updated(transition: .easeInOut(duration: 0.2)) state.updated(transition: .easeInOut(duration: 0.2))
} }
@ -1594,10 +1610,10 @@ private final class DrawingScreenComponent: CombinedComponent {
var updatedTailPosition = entity.tailPosition var updatedTailPosition = entity.tailPosition
updatedTailPosition.x = 1.0 - updatedTailPosition.x updatedTailPosition.x = 1.0 - updatedTailPosition.x
entity.tailPosition = updatedTailPosition entity.tailPosition = updatedTailPosition
entity.currentEntityView?.update() updateEntityView.invoke((entity.uuid, false))
} else if let entity = state.selectedEntity as? DrawingStickerEntity { } else if let entity = state.selectedEntity as? DrawingStickerEntity {
entity.mirrored = !entity.mirrored entity.mirrored = !entity.mirrored
entity.currentEntityView?.update(animated: true) updateEntityView.invoke((entity.uuid, true))
} }
state.updated(transition: .easeInOut(duration: 0.2)) state.updated(transition: .easeInOut(duration: 0.2))
} }
@ -1616,9 +1632,9 @@ private final class DrawingScreenComponent: CombinedComponent {
var sizeSliderVisible = false var sizeSliderVisible = false
var isEditingText = false var isEditingText = false
var sizeValue: CGFloat? var sizeValue: CGFloat?
if let textEntity = state.selectedEntity as? DrawingTextEntity, let entityView = textEntity.currentEntityView as? DrawingTextEntityView { if let textEntity = state.selectedEntity as? DrawingTextEntity, !"".isEmpty {//} let entityView = textEntity.currentEntityView as? DrawingTextEntityView {
sizeSliderVisible = true sizeSliderVisible = true
isEditingText = entityView.isEditing isEditingText = false//entityView.isEditing
sizeValue = textEntity.fontSize sizeValue = textEntity.fontSize
} else { } else {
if state.selectedEntity == nil || !(state.selectedEntity is DrawingStickerEntity) { if state.selectedEntity == nil || !(state.selectedEntity is DrawingStickerEntity) {
@ -1755,8 +1771,8 @@ private final class DrawingScreenComponent: CombinedComponent {
Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: .white) Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: .white)
), ),
action: { [weak state] in action: { [weak state] in
if let entity = state?.selectedEntity as? DrawingTextEntity, let entityView = entity.currentEntityView as? DrawingTextEntityView { if let entity = state?.selectedEntity as? DrawingTextEntity {
entityView.endEditing(reset: true) endEditingTextEntityView.invoke((entity.uuid, true))
} }
} }
), ),
@ -1775,8 +1791,8 @@ private final class DrawingScreenComponent: CombinedComponent {
Text(text: environment.strings.Common_Done, font: Font.semibold(17.0), color: .white) Text(text: environment.strings.Common_Done, font: Font.semibold(17.0), color: .white)
), ),
action: { [weak state] in action: { [weak state] in
if let entity = state?.selectedEntity as? DrawingTextEntity, let entityView = entity.currentEntityView as? DrawingTextEntityView { if let entity = state?.selectedEntity as? DrawingTextEntity {
entityView.endEditing() endEditingTextEntityView.invoke((entity.uuid, false))
} }
} }
), ),
@ -2041,6 +2057,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
private let toggleWithPreviousTool: ActionSlot<Void> private let toggleWithPreviousTool: ActionSlot<Void>
fileprivate let insertSticker: ActionSlot<Void> fileprivate let insertSticker: ActionSlot<Void>
fileprivate let insertText: ActionSlot<Void> fileprivate let insertText: ActionSlot<Void>
private let updateEntityView: ActionSlot<(UUID, Bool)>
private let endEditingTextEntityView: ActionSlot<(UUID, Bool)>
private let apply: ActionSlot<Void> private let apply: ActionSlot<Void>
private let dismiss: ActionSlot<Void> private let dismiss: ActionSlot<Void>
@ -2274,6 +2292,18 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
} }
} }
} }
self.updateEntityView.connect { [weak self] uuid, animated in
if let strongSelf = self, let entitiesView = strongSelf._entitiesView {
entitiesView.getView(for: uuid)?.update(animated: animated)
}
}
self.endEditingTextEntityView.connect { [weak self] uuid, reset in
if let strongSelf = self, let entitiesView = strongSelf._entitiesView {
if let textEntityView = entitiesView.getView(for: uuid) as? DrawingTextEntityView {
textEntityView.endEditing(reset: reset)
}
}
}
} }
return self._entitiesView! return self._entitiesView!
} }
@ -2312,6 +2342,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
self.toggleWithPreviousTool = ActionSlot<Void>() self.toggleWithPreviousTool = ActionSlot<Void>()
self.insertSticker = ActionSlot<Void>() self.insertSticker = ActionSlot<Void>()
self.insertText = ActionSlot<Void>() self.insertText = ActionSlot<Void>()
self.updateEntityView = ActionSlot<(UUID, Bool)>()
self.endEditingTextEntityView = ActionSlot<(UUID, Bool)>()
self.apply = ActionSlot<Void>() self.apply = ActionSlot<Void>()
self.dismiss = ActionSlot<Void>() self.dismiss = ActionSlot<Void>()
@ -2737,6 +2769,8 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
toggleWithPreviousTool: self.toggleWithPreviousTool, toggleWithPreviousTool: self.toggleWithPreviousTool,
insertSticker: self.insertSticker, insertSticker: self.insertSticker,
insertText: self.insertText, insertText: self.insertText,
updateEntityView: self.updateEntityView,
endEditingTextEntityView: self.endEditingTextEntityView,
apply: self.apply, apply: self.apply,
dismiss: self.dismiss, dismiss: self.dismiss,
presentColorPicker: { [weak self] initialColor in presentColorPicker: { [weak self] initialColor in
@ -3042,6 +3076,24 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
self.drawingView.addInteraction(dropInteraction) self.drawingView.addInteraction(dropInteraction)
} }
public func generateDrawingResultData() -> DrawingResultData? {
if self.drawingView.isEmpty && self.entitiesView.entities.isEmpty {
return nil
}
let drawingImage = generateImage(self.drawingView.imageSize, contextGenerator: { size, context in
let bounds = CGRect(origin: .zero, size: size)
context.clear(bounds)
if let cgImage = self.drawingView.drawingImage?.cgImage {
context.draw(cgImage, in: bounds)
}
}, opaque: false, scale: 1.0)
let _ = self.entitiesView.entitiesData
let codableEntities = self.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }.compactMap({ CodableDrawingEntity(entity: $0) })
return DrawingResultData(data: self.drawingView.drawingData, drawingImage: drawingImage, entities: codableEntities)
}
public func generateResultData() -> TGPaintingData? { public func generateResultData() -> TGPaintingData? {
if self.drawingView.isEmpty && self.entitiesView.entities.isEmpty { if self.drawingView.isEmpty && self.entitiesView.entities.isEmpty {
return nil return nil
@ -3102,7 +3154,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
stickers.append(coder.makeData()) stickers.append(coder.makeData())
} else if let text = entity as? DrawingTextEntity, let subEntities = text.renderSubEntities { } else if let text = entity as? DrawingTextEntity, let subEntities = text.renderSubEntities {
for sticker in subEntities { for sticker in subEntities {
if case let .file(file) = sticker.content { if let sticker = sticker as? DrawingStickerEntity, case let .file(file) = sticker.content {
let coder = PostboxEncoder() let coder = PostboxEncoder()
coder.encodeRootObject(file) coder.encodeRootObject(file)
stickers.append(coder.makeData()) stickers.append(coder.makeData())

View File

@ -2,125 +2,7 @@ import Foundation
import UIKit import UIKit
import Display import Display
import AccountContext import AccountContext
import MediaEditor
public final class DrawingSimpleShapeEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case shapeType
case drawType
case color
case lineWidth
case referenceDrawingSize
case position
case size
case rotation
case renderImage
}
public enum ShapeType: Codable {
case rectangle
case ellipse
case star
}
public enum DrawType: Codable {
case fill
case stroke
}
public let uuid: UUID
public let isAnimated: Bool
var shapeType: ShapeType
var drawType: DrawType
public var color: DrawingColor
public var lineWidth: CGFloat
var referenceDrawingSize: CGSize
public var position: CGPoint
public var size: CGSize
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var isMedia: Bool {
return false
}
init(shapeType: ShapeType, drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.shapeType = shapeType
self.drawType = drawType
self.color = color
self.lineWidth = lineWidth
self.referenceDrawingSize = .zero
self.position = .zero
self.size = CGSize(width: 1.0, height: 1.0)
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.shapeType = try container.decode(ShapeType.self, forKey: .shapeType)
self.drawType = try container.decode(DrawType.self, forKey: .drawType)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.size = try container.decode(CGSize.self, forKey: .size)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.shapeType, forKey: .shapeType)
try container.encode(self.drawType, forKey: .drawType)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.size, forKey: .size)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingSimpleShapeEntity(shapeType: self.shapeType, drawType: self.drawType, color: self.color, lineWidth: self.lineWidth)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.size = self.size
newEntity.rotation = self.rotation
return newEntity
}
public weak var currentEntityView: DrawingEntityView?
public func makeView(context: AccountContext) -> DrawingEntityView {
let entityView = DrawingSimpleShapeEntityView(context: context, entity: self)
self.currentEntityView = entityView
return entityView
}
public func prepareForRender() {
self.renderImage = (self.currentEntityView as? DrawingSimpleShapeEntityView)?.getRenderImage()
}
}
final class DrawingSimpleShapeEntityView: DrawingEntityView { final class DrawingSimpleShapeEntityView: DrawingEntityView {
private var shapeEntity: DrawingSimpleShapeEntity { private var shapeEntity: DrawingSimpleShapeEntity {

View File

@ -7,121 +7,7 @@ import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import StickerResources import StickerResources
import AccountContext import AccountContext
import MediaEditor
public final class DrawingStickerEntity: DrawingEntity, Codable {
public enum Content {
case file(TelegramMediaFile)
case image(UIImage)
}
private enum CodingKeys: String, CodingKey {
case uuid
case file
case image
case referenceDrawingSize
case position
case scale
case rotation
case mirrored
}
public let uuid: UUID
public let content: Content
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var scale: CGFloat
public var rotation: CGFloat
public var mirrored: Bool
public var color: DrawingColor = DrawingColor.clear
public var lineWidth: CGFloat = 0.0
public var center: CGPoint {
return self.position
}
public var baseSize: CGSize {
let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.2)
return CGSize(width: size, height: size)
}
public var isAnimated: Bool {
switch self.content {
case let .file(file):
return file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm"
case .image:
return false
}
}
public var isMedia: Bool {
return false
}
public init(content: Content) {
self.uuid = UUID()
self.content = content
self.referenceDrawingSize = .zero
self.position = CGPoint()
self.scale = 1.0
self.rotation = 0.0
self.mirrored = false
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) {
self.content = .file(file)
} else if let imageData = try container.decodeIfPresent(Data.self, forKey: .image), let image = UIImage(data: imageData) {
self.content = .image(image)
} else {
fatalError()
}
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.mirrored = try container.decode(Bool.self, forKey: .mirrored)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
switch self.content {
case let .file(file):
try container.encode(file, forKey: .file)
case let .image(image):
try container.encodeIfPresent(image.pngData(), forKey: .image)
}
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.mirrored, forKey: .mirrored)
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingStickerEntity(content: self.content)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.scale = self.scale
newEntity.rotation = self.rotation
newEntity.mirrored = self.mirrored
return newEntity
}
public weak var currentEntityView: DrawingEntityView?
public func makeView(context: AccountContext) -> DrawingEntityView {
let entityView = DrawingStickerEntityView(context: context, entity: self)
self.currentEntityView = entityView
return entityView
}
public func prepareForRender() {
}
}
final class DrawingStickerEntityView: DrawingEntityView { final class DrawingStickerEntityView: DrawingEntityView {
private var stickerEntity: DrawingStickerEntity { private var stickerEntity: DrawingStickerEntity {
@ -274,7 +160,8 @@ final class DrawingStickerEntityView: DrawingEntityView {
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0)) let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm") let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm")
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) let pathPrefix = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
self.animationNode?.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384) self.cachedDisposable.set((source.cachedDataPath(width: 384, height: 384)
|> deliverOn(Queue.concurrentDefaultQueue())).start()) |> deliverOn(Queue.concurrentDefaultQueue())).start())

View File

@ -5,286 +5,17 @@ import SwiftSignalKit
import AccountContext import AccountContext
import TextFormat import TextFormat
import EmojiTextAttachmentView import EmojiTextAttachmentView
import MediaEditor
public final class DrawingTextEntity: DrawingEntity, Codable { extension DrawingTextEntity.Alignment {
final class CustomEmojiAttribute: Codable { var alignment: NSTextAlignment {
private enum CodingKeys: String, CodingKey { switch self {
case attribute case .left:
case rangeOrigin return .left
case rangeLength case .center:
} return .center
let attribute: ChatTextInputTextCustomEmojiAttribute case .right:
let range: NSRange return .right
init(attribute: ChatTextInputTextCustomEmojiAttribute, range: NSRange) {
self.attribute = attribute
self.range = range
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.attribute = try container.decode(ChatTextInputTextCustomEmojiAttribute.self, forKey: .attribute)
let rangeOrigin = try container.decode(Int.self, forKey: .rangeOrigin)
let rangeLength = try container.decode(Int.self, forKey: .rangeLength)
self.range = NSMakeRange(rangeOrigin, rangeLength)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.attribute, forKey: .attribute)
try container.encode(self.range.location, forKey: .rangeOrigin)
try container.encode(self.range.length, forKey: .rangeLength)
}
}
private enum CodingKeys: String, CodingKey {
case uuid
case text
case textAttributes
case style
case animation
case font
case alignment
case fontSize
case color
case referenceDrawingSize
case position
case width
case scale
case rotation
case renderImage
case renderSubEntities
case renderAnimationFrames
}
enum Style: Codable {
case regular
case filled
case semi
case stroke
}
enum Animation: Codable {
case none
case typing
case wiggle
case zoomIn
}
enum Font: Codable {
case sanFrancisco
case other(String, String)
}
enum Alignment: Codable {
case left
case center
case right
var alignment: NSTextAlignment {
switch self {
case .left:
return .left
case .center:
return .center
case .right:
return .right
}
}
}
public var uuid: UUID
public var isAnimated: Bool {
if self.animation != .none {
return true
}
var isAnimated = false
self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in
if let _ = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
isAnimated = true
}
})
return isAnimated
}
var text: NSAttributedString
var style: Style
var animation: Animation
var font: Font
var alignment: Alignment
var fontSize: CGFloat
public var color: DrawingColor
public var lineWidth: CGFloat = 0.0
var referenceDrawingSize: CGSize
public var position: CGPoint
var width: CGFloat
public var scale: CGFloat
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingStickerEntity]?
public var isMedia: Bool {
return false
}
public class AnimationFrame: Codable {
private enum CodingKeys: String, CodingKey {
case timestamp
case duration
case image
}
public let timestamp: Double
public let duration: Double
public let image: UIImage
public init(timestamp: Double, duration: Double, image: UIImage) {
self.timestamp = timestamp
self.duration = duration
self.image = image
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.timestamp = try container.decode(Double.self, forKey: .timestamp)
self.duration = try container.decode(Double.self, forKey: .duration)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .image) {
self.image = UIImage(data: renderImageData)!
} else {
fatalError()
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.timestamp, forKey: .timestamp)
try container.encode(self.duration, forKey: .duration)
if let data = self.image.pngData() {
try container.encode(data, forKey: .image)
}
}
}
public var renderAnimationFrames: [AnimationFrame]?
init(text: NSAttributedString, style: Style, animation: Animation, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) {
self.uuid = UUID()
self.text = text
self.style = style
self.animation = animation
self.font = font
self.alignment = alignment
self.fontSize = fontSize
self.color = color
self.referenceDrawingSize = .zero
self.position = .zero
self.width = 100.0
self.scale = 1.0
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
let text = try container.decode(String.self, forKey: .text)
let attributedString = NSMutableAttributedString(string: text)
let textAttributes = try container.decode([CustomEmojiAttribute].self, forKey: .textAttributes)
for attribute in textAttributes {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: attribute.attribute, range: attribute.range)
}
self.text = attributedString
self.style = try container.decode(Style.self, forKey: .style)
self.animation = try container.decode(Animation.self, forKey: .animation)
self.font = try container.decode(Font.self, forKey: .font)
self.alignment = try container.decode(Alignment.self, forKey: .alignment)
self.fontSize = try container.decode(CGFloat.self, forKey: .fontSize)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.width = try container.decode(CGFloat.self, forKey: .width)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
if let renderSubEntities = try? container.decodeIfPresent([CodableDrawingEntity].self, forKey: .renderSubEntities) {
self.renderSubEntities = renderSubEntities.compactMap { $0.entity as? DrawingStickerEntity }
}
self.renderAnimationFrames = try container.decodeIfPresent([AnimationFrame].self, forKey: .renderAnimationFrames)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.text.string, forKey: .text)
var textAttributes: [CustomEmojiAttribute] = []
self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in
if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
textAttributes.append(CustomEmojiAttribute(attribute: value, range: range))
}
})
try container.encode(textAttributes, forKey: .textAttributes)
try container.encode(self.style, forKey: .style)
try container.encode(self.animation, forKey: .animation)
try container.encode(self.font, forKey: .font)
try container.encode(self.alignment, forKey: .alignment)
try container.encode(self.fontSize, forKey: .fontSize)
try container.encode(self.color, forKey: .color)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.width, forKey: .width)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
if let renderSubEntities = self.renderSubEntities {
let codableEntities: [CodableDrawingEntity] = renderSubEntities.map { .sticker($0) }
try container.encode(codableEntities, forKey: .renderSubEntities)
}
if let renderAnimationFrames = self.renderAnimationFrames {
try container.encode(renderAnimationFrames, forKey: .renderAnimationFrames)
}
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingTextEntity(text: self.text, style: self.style, animation: self.animation, font: self.font, alignment: self.alignment, fontSize: self.fontSize, color: self.color)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.width = self.width
newEntity.scale = self.scale
newEntity.rotation = self.rotation
return newEntity
}
public weak var currentEntityView: DrawingEntityView?
public func makeView(context: AccountContext) -> DrawingEntityView {
let entityView = DrawingTextEntityView(context: context, entity: self)
self.currentEntityView = entityView
return entityView
}
public func prepareForRender() {
self.renderImage = (self.currentEntityView as? DrawingTextEntityView)?.getRenderImage()
self.renderSubEntities = (self.currentEntityView as? DrawingTextEntityView)?.getRenderSubEntities()
if case .none = self.animation {
self.renderAnimationFrames = nil
} else {
self.renderAnimationFrames = (self.currentEntityView as? DrawingTextEntityView)?.getRenderAnimationFrames()
} }
} }
} }
@ -892,7 +623,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
return image return image
} }
func getRenderSubEntities() -> [DrawingStickerEntity] { func getRenderSubEntities() -> [DrawingEntity] {
let textSize = self.textView.bounds.size let textSize = self.textView.bounds.size
let textPosition = self.textEntity.position let textPosition = self.textEntity.position
let scale = self.textEntity.scale let scale = self.textEntity.scale
@ -900,7 +631,7 @@ final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate {
let itemSize: CGFloat = floor(24.0 * self.displayFontSize * 0.78 / 17.0) let itemSize: CGFloat = floor(24.0 * self.displayFontSize * 0.78 / 17.0)
var entities: [DrawingStickerEntity] = [] var entities: [DrawingEntity] = []
for (emojiRect, emojiAttribute) in self.emojiRects { for (emojiRect, emojiAttribute) in self.emojiRects {
guard let file = emojiAttribute.file else { guard let file = emojiAttribute.file else {
continue continue

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import UIKit import UIKit
import Display import Display
import MediaEditor
final class MarkerTool: DrawingElement { final class MarkerTool: DrawingElement {
let uuid: UUID let uuid: UUID

View File

@ -2,164 +2,7 @@ import Foundation
import UIKit import UIKit
import QuartzCore import QuartzCore
import simd import simd
import MediaEditor
public struct DrawingColor: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case red
case green
case blue
case alpha
case position
}
public static var clear = DrawingColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
public var red: CGFloat
public var green: CGFloat
public var blue: CGFloat
public var alpha: CGFloat
public var position: CGPoint?
var isClear: Bool {
return self.red.isZero && self.green.isZero && self.blue.isZero && self.alpha.isZero
}
public init(
red: CGFloat,
green: CGFloat,
blue: CGFloat,
alpha: CGFloat = 1.0,
position: CGPoint? = nil
) {
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
self.position = position
}
public init(color: UIColor) {
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var alpha: CGFloat = 1.0
if color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
self.init(red: red, green: green, blue: blue, alpha: alpha)
} else if color.getWhite(&red, alpha: &alpha) {
self.init(red: red, green: red, blue: red, alpha: alpha)
} else {
self.init(red: 0.0, green: 0.0, blue: 0.0)
}
}
public init(rgb: UInt32) {
self.init(color: UIColor(rgb: rgb))
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.red = try container.decode(CGFloat.self, forKey: .red)
self.green = try container.decode(CGFloat.self, forKey: .green)
self.blue = try container.decode(CGFloat.self, forKey: .blue)
self.alpha = try container.decode(CGFloat.self, forKey: .alpha)
self.position = try container.decodeIfPresent(CGPoint.self, forKey: .position)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.red, forKey: .red)
try container.encode(self.green, forKey: .green)
try container.encode(self.blue, forKey: .blue)
try container.encode(self.alpha, forKey: .alpha)
try container.encodeIfPresent(self.position, forKey: .position)
}
func withUpdatedRed(_ red: CGFloat) -> DrawingColor {
return DrawingColor(
red: red,
green: self.green,
blue: self.blue,
alpha: self.alpha
)
}
func withUpdatedGreen(_ green: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: green,
blue: self.blue,
alpha: self.alpha
)
}
func withUpdatedBlue(_ blue: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: blue,
alpha: self.alpha
)
}
func withUpdatedAlpha(_ alpha: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: alpha,
position: self.position
)
}
func withUpdatedPosition(_ position: CGPoint) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: self.alpha,
position: position
)
}
func toUIColor() -> UIColor {
return UIColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: self.alpha
)
}
func toCGColor() -> CGColor {
return self.toUIColor().cgColor
}
func toFloat4() -> vector_float4 {
return [
simd_float1(self.red),
simd_float1(self.green),
simd_float1(self.blue),
simd_float1(self.alpha)
]
}
public static func ==(lhs: DrawingColor, rhs: DrawingColor) -> Bool {
if lhs.red != rhs.red {
return false
}
if lhs.green != rhs.green {
return false
}
if lhs.blue != rhs.blue {
return false
}
if lhs.alpha != rhs.alpha {
return false
}
return true
}
}
extension UIBezierPath { extension UIBezierPath {
convenience init(roundRect rect: CGRect, topLeftRadius: CGFloat = 0.0, topRightRadius: CGFloat = 0.0, bottomLeftRadius: CGFloat = 0.0, bottomRightRadius: CGFloat = 0.0) { convenience init(roundRect rect: CGRect, topLeftRadius: CGFloat = 0.0, topRightRadius: CGFloat = 0.0, bottomLeftRadius: CGFloat = 0.0, bottomRightRadius: CGFloat = 0.0) {

View File

@ -2,133 +2,7 @@ import Foundation
import UIKit import UIKit
import Display import Display
import AccountContext import AccountContext
import MediaEditor
public final class DrawingVectorEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case type
case color
case lineWidth
case drawingSize
case referenceDrawingSize
case start
case mid
case end
case renderImage
}
public enum VectorType: Codable {
case line
case oneSidedArrow
case twoSidedArrow
}
public let uuid: UUID
public let isAnimated: Bool
var type: VectorType
public var color: DrawingColor
public var lineWidth: CGFloat
public var drawingSize: CGSize
var referenceDrawingSize: CGSize
var start: CGPoint
var mid: (CGFloat, CGFloat)
var end: CGPoint
var _cachedMidPoint: (start: CGPoint, end: CGPoint, midLength: CGFloat, midHeight: CGFloat, midPoint: CGPoint)?
var midPoint: CGPoint {
if let (start, end, midLength, midHeight, midPoint) = self._cachedMidPoint, start == self.start, end == self.end, midLength == self.mid.0, midHeight == self.mid.1 {
return midPoint
} else {
let midPoint = midPointPositionFor(start: self.start, end: self.end, length: self.mid.0, height: self.mid.1)
self._cachedMidPoint = (self.start, self.end, self.mid.0, self.mid.1, midPoint)
return midPoint
}
}
public var center: CGPoint {
return self.start
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var isMedia: Bool {
return false
}
init(type: VectorType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.type = type
self.color = color
self.lineWidth = lineWidth
self.drawingSize = .zero
self.referenceDrawingSize = .zero
self.start = CGPoint()
self.mid = (0.5, 0.0)
self.end = CGPoint()
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.type = try container.decode(VectorType.self, forKey: .type)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.start = try container.decode(CGPoint.self, forKey: .start)
let mid = try container.decode(CGPoint.self, forKey: .mid)
self.mid = (mid.x, mid.y)
self.end = try container.decode(CGPoint.self, forKey: .end)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.type, forKey: .type)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.drawingSize, forKey: .drawingSize)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.start, forKey: .start)
try container.encode(CGPoint(x: self.mid.0, y: self.mid.1), forKey: .mid)
try container.encode(self.end, forKey: .end)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingVectorEntity(type: self.type, color: self.color, lineWidth: self.lineWidth)
newEntity.drawingSize = self.drawingSize
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.start = self.start
newEntity.mid = self.mid
newEntity.end = self.end
return newEntity
}
public weak var currentEntityView: DrawingEntityView?
public func makeView(context: AccountContext) -> DrawingEntityView {
let entityView = DrawingVectorEntityView(context: context, entity: self)
self.currentEntityView = entityView
return entityView
}
public func prepareForRender() {
self.renderImage = (self.currentEntityView as? DrawingVectorEntityView)?.getRenderImage()
}
}
final class DrawingVectorEntityView: DrawingEntityView { final class DrawingVectorEntityView: DrawingEntityView {
private var vectorEntity: DrawingVectorEntity { private var vectorEntity: DrawingVectorEntity {
@ -166,7 +40,7 @@ final class DrawingVectorEntityView: DrawingEntityView {
self.shapeLayer.path = CGPath.curve( self.shapeLayer.path = CGPath.curve(
start: self.vectorEntity.start, start: self.vectorEntity.start,
end: self.vectorEntity.end, end: self.vectorEntity.end,
mid: self.vectorEntity.midPoint, mid: self.midPoint,
lineWidth: lineWidth, lineWidth: lineWidth,
arrowSize: self.vectorEntity.type == .line ? nil : CGSize(width: lineWidth * 1.5, height: lineWidth * 3.0), arrowSize: self.vectorEntity.type == .line ? nil : CGSize(width: lineWidth * 1.5, height: lineWidth * 3.0),
twoSided: self.vectorEntity.type == .twoSidedArrow twoSided: self.vectorEntity.type == .twoSidedArrow
@ -198,7 +72,7 @@ final class DrawingVectorEntityView: DrawingEntityView {
let expandedPath = CGPath.curve( let expandedPath = CGPath.curve(
start: self.vectorEntity.start, start: self.vectorEntity.start,
end: self.vectorEntity.end, end: self.vectorEntity.end,
mid: self.vectorEntity.midPoint, mid: self.midPoint,
lineWidth: self.maxLineWidth * 0.8, lineWidth: self.maxLineWidth * 0.8,
arrowSize: nil, arrowSize: nil,
twoSided: false twoSided: false
@ -227,6 +101,18 @@ final class DrawingVectorEntityView: DrawingEntityView {
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
return image return image
} }
var _cachedMidPoint: (start: CGPoint, end: CGPoint, midLength: CGFloat, midHeight: CGFloat, midPoint: CGPoint)?
var midPoint: CGPoint {
let entity = self.vectorEntity
if let (start, end, midLength, midHeight, midPoint) = self._cachedMidPoint, start == entity.start, end == entity.end, midLength == entity.mid.0, midHeight == entity.mid.1 {
return midPoint
} else {
let midPoint = midPointPositionFor(start: entity.start, end: entity.end, length: entity.mid.0, height: entity.mid.1)
self._cachedMidPoint = (entity.start, entity.end, entity.mid.0, entity.mid.1, midPoint)
return midPoint
}
}
} }
private func midPointPositionFor(start: CGPoint, end: CGPoint, length: CGFloat, height: CGFloat) -> CGPoint { private func midPointPositionFor(start: CGPoint, end: CGPoint, length: CGFloat, height: CGFloat) -> CGPoint {
@ -295,7 +181,7 @@ final class DrawingVectorEntititySelectionView: DrawingEntitySelectionView, UIGe
private var currentHandle: CALayer? private var currentHandle: CALayer?
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) { @objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let entityView = self.entityView, let entity = entityView.entity as? DrawingVectorEntity else { guard let entityView = self.entityView as? DrawingVectorEntityView, let entity = entityView.entity as? DrawingVectorEntity else {
return return
} }
let location = gestureRecognizer.location(in: self) let location = gestureRecognizer.location(in: self)
@ -325,7 +211,7 @@ final class DrawingVectorEntititySelectionView: DrawingEntitySelectionView, UIGe
updatedEnd.x += delta.x updatedEnd.x += delta.x
updatedEnd.y += delta.y updatedEnd.y += delta.y
} else if self.currentHandle === self.midHandle { } else if self.currentHandle === self.midHandle {
var updatedMidPoint = entity.midPoint var updatedMidPoint = entityView.midPoint
updatedMidPoint.x += delta.x updatedMidPoint.x += delta.x
updatedMidPoint.y += delta.y updatedMidPoint.y += delta.y
@ -356,7 +242,7 @@ final class DrawingVectorEntititySelectionView: DrawingEntitySelectionView, UIGe
entity.start = updatedStart entity.start = updatedStart
entity.mid = updatedMid entity.mid = updatedMid
entity.end = updatedEnd entity.end = updatedEnd
entityView.update() entityView.update(animated: false)
gestureRecognizer.setTranslation(.zero, in: entityView) gestureRecognizer.setTranslation(.zero, in: entityView)
case .ended: case .ended:
@ -391,7 +277,7 @@ final class DrawingVectorEntititySelectionView: DrawingEntitySelectionView, UIGe
self.startHandle.lineWidth = lineWidth self.startHandle.lineWidth = lineWidth
self.midHandle.path = handlePath self.midHandle.path = handlePath
self.midHandle.position = entity.midPoint self.midHandle.position = entityView.midPoint
self.midHandle.bounds = bounds self.midHandle.bounds = bounds
self.midHandle.lineWidth = lineWidth self.midHandle.lineWidth = lineWidth

View File

@ -6,6 +6,7 @@ import ComponentFlow
import LegacyComponents import LegacyComponents
import AppBundle import AppBundle
import ImageBlur import ImageBlur
import MediaEditor
protocol DrawingRenderLayer: CALayer { protocol DrawingRenderLayer: CALayer {

View File

@ -2,6 +2,7 @@ import Foundation
import UIKit import UIKit
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import MediaEditor
private let size = CGSize(width: 148.0, height: 148.0) private let size = CGSize(width: 148.0, height: 148.0)
private let outerWidth: CGFloat = 12.0 private let outerWidth: CGFloat = 12.0

View File

@ -5,6 +5,7 @@ import ComponentFlow
import LegacyComponents import LegacyComponents
import TelegramCore import TelegramCore
import LottieAnimationComponent import LottieAnimationComponent
import MediaEditor
enum DrawingTextStyle: Equatable { enum DrawingTextStyle: Equatable {
case regular case regular

View File

@ -30,6 +30,7 @@ swift_library(
"//submodules/AttachmentUI:AttachmentUI", "//submodules/AttachmentUI:AttachmentUI",
"//submodules/DrawingUI:DrawingUI", "//submodules/DrawingUI:DrawingUI",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/TelegramUI/Components/MediaEditor",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -10,6 +10,7 @@ import YuvConversion
import StickerResources import StickerResources
import DrawingUI import DrawingUI
import SolidRoundedButtonNode import SolidRoundedButtonNode
import MediaEditor
protocol LegacyPaintEntity { protocol LegacyPaintEntity {
var position: CGPoint { get } var position: CGPoint { get }
@ -432,7 +433,9 @@ public final class LegacyPaintEntityRenderer: NSObject, TGPhotoPaintEntityRender
renderEntities.append(LegacyPaintTextEntity(entity: text)) renderEntities.append(LegacyPaintTextEntity(entity: text))
if let renderSubEntities = text.renderSubEntities, let account { if let renderSubEntities = text.renderSubEntities, let account {
for entity in renderSubEntities { for entity in renderSubEntities {
renderEntities.append(LegacyPaintStickerEntity(account: account, entity: entity)) if let entity = entity as? DrawingStickerEntity {
renderEntities.append(LegacyPaintStickerEntity(account: account, entity: entity))
}
} }
} }
} else if let simpleShape = entity as? DrawingSimpleShapeEntity { } else if let simpleShape = entity as? DrawingSimpleShapeEntity {

View File

@ -497,9 +497,25 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
}) })
let dataSignal: Signal<PeersNearbyData?, NoError> = coordinatePromise.get() let dataSignal: Signal<PeersNearbyData?, NoError> = coordinatePromise.get()
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs?.latitude == rhs?.latitude && lhs?.longitude == rhs?.longitude
})
|> mapToSignal { coordinate -> Signal<PeersNearbyData?, NoError> in |> mapToSignal { coordinate -> Signal<PeersNearbyData?, NoError> in
guard let coordinate = coordinate else { guard let coordinate = coordinate else {
return .single(nil) let peersNearbyContext = PeersNearbyContext(network: context.account.network, stateManager: context.account.stateManager, coordinate: nil)
return peersNearbyContext.get()
|> map { peersNearby -> PeersNearbyData in
var isVisible = false
if let peersNearby {
for peer in peersNearby {
if case .selfPeer = peer {
isVisible = true
break
}
}
}
return PeersNearbyData(latitude: 0.0, longitude: 0.0, address: nil, visible: isVisible, accountPeerId: context.account.peerId, users: [], groups: [], channels: [])
}
} }
return Signal { subscriber in return Signal { subscriber in

View File

@ -14,7 +14,7 @@ func _internal_requestAccountPrivacySettings(account: Account) -> Signal<Account
let phoneNumberPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyPhoneNumber)) let phoneNumberPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyPhoneNumber))
let phoneDiscoveryPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyAddedByPhone)) let phoneDiscoveryPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyAddedByPhone))
let voiceMessagesPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyVoiceMessages)) let voiceMessagesPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyVoiceMessages))
let bioPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyProfilePhoto)) let bioPrivacy = account.network.request(Api.functions.account.getPrivacy(key: .inputPrivacyKeyAbout))
let autoremoveTimeout = account.network.request(Api.functions.account.getAccountTTL()) let autoremoveTimeout = account.network.request(Api.functions.account.getAccountTTL())
let globalPrivacySettings = account.network.request(Api.functions.account.getGlobalPrivacySettings()) let globalPrivacySettings = account.network.request(Api.functions.account.getGlobalPrivacySettings())
let messageAutoremoveTimeout = account.network.request(Api.functions.messages.getDefaultHistoryTTL()) let messageAutoremoveTimeout = account.network.request(Api.functions.messages.getDefaultHistoryTTL())
@ -253,7 +253,7 @@ public enum UpdateSelectiveAccountPrivacySettingsType {
case .voiceMessages: case .voiceMessages:
return .inputPrivacyKeyVoiceMessages return .inputPrivacyKeyVoiceMessages
case .bio: case .bio:
return .inputPrivacyKeyProfilePhoto return .inputPrivacyKeyAbout
} }
} }
} }

View File

@ -38,6 +38,7 @@ swift_library(
"//submodules/StickerResources:StickerResources", "//submodules/StickerResources:StickerResources",
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache", "//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView", "//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
"//submodules/TelegramUI/Components/MediaEditor",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -23,6 +23,7 @@ import DrawingUI
import SolidRoundedButtonComponent import SolidRoundedButtonComponent
import AnimationCache import AnimationCache
import EmojiTextAttachmentView import EmojiTextAttachmentView
import MediaEditor
enum AvatarBackground: Equatable { enum AvatarBackground: Equatable {
case gradient([UInt32]) case gradient([UInt32])

View File

@ -61,6 +61,11 @@ swift_library(
"//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext", "//submodules/AccountContext:AccountContext",
"//submodules/AppBundle:AppBundle", "//submodules/AppBundle:AppBundle",
"//submodules/TextFormat:TextFormat",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/StickerResources:StickerResources",
"//submodules/YuvConversion:YuvConversion",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -0,0 +1,93 @@
import Foundation
public enum CodableDrawingEntity {
case sticker(DrawingStickerEntity)
case text(DrawingTextEntity)
case simpleShape(DrawingSimpleShapeEntity)
case bubble(DrawingBubbleEntity)
case vector(DrawingVectorEntity)
public init?(entity: DrawingEntity) {
if let entity = entity as? DrawingStickerEntity {
self = .sticker(entity)
} else if let entity = entity as? DrawingTextEntity {
self = .text(entity)
} else if let entity = entity as? DrawingSimpleShapeEntity {
self = .simpleShape(entity)
} else if let entity = entity as? DrawingBubbleEntity {
self = .bubble(entity)
} else if let entity = entity as? DrawingVectorEntity {
self = .vector(entity)
} else {
return nil
}
}
public var entity: DrawingEntity {
switch self {
case let .sticker(entity):
return entity
case let .text(entity):
return entity
case let .simpleShape(entity):
return entity
case let .bubble(entity):
return entity
case let .vector(entity):
return entity
}
}
}
extension CodableDrawingEntity: Codable {
private enum CodingKeys: String, CodingKey {
case type
case entity
}
private enum EntityType: Int, Codable {
case sticker
case text
case simpleShape
case bubble
case vector
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(EntityType.self, forKey: .type)
switch type {
case .sticker:
self = .sticker(try container.decode(DrawingStickerEntity.self, forKey: .entity))
case .text:
self = .text(try container.decode(DrawingTextEntity.self, forKey: .entity))
case .simpleShape:
self = .simpleShape(try container.decode(DrawingSimpleShapeEntity.self, forKey: .entity))
case .bubble:
self = .bubble(try container.decode(DrawingBubbleEntity.self, forKey: .entity))
case .vector:
self = .vector(try container.decode(DrawingVectorEntity.self, forKey: .entity))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .sticker(payload):
try container.encode(EntityType.sticker, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .text(payload):
try container.encode(EntityType.text, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .simpleShape(payload):
try container.encode(EntityType.simpleShape, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .bubble(payload):
try container.encode(EntityType.bubble, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .vector(payload):
try container.encode(EntityType.vector, forKey: .type)
try container.encode(payload, forKey: .entity)
}
}
}

View File

@ -0,0 +1,107 @@
import Foundation
import UIKit
import Display
import AccountContext
public final class DrawingBubbleEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case drawType
case color
case lineWidth
case referenceDrawingSize
case position
case size
case rotation
case tailPosition
case renderImage
}
public enum DrawType: Codable {
case fill
case stroke
}
public let uuid: UUID
public let isAnimated: Bool
public var drawType: DrawType
public var color: DrawingColor
public var lineWidth: CGFloat
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var size: CGSize
public var rotation: CGFloat
public var tailPosition: CGPoint
public var center: CGPoint {
return self.position
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.drawType = drawType
self.color = color
self.lineWidth = lineWidth
self.referenceDrawingSize = .zero
self.position = .zero
self.size = CGSize(width: 1.0, height: 1.0)
self.rotation = 0.0
self.tailPosition = CGPoint(x: 0.16, y: 0.18)
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.drawType = try container.decode(DrawType.self, forKey: .drawType)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.size = try container.decode(CGSize.self, forKey: .size)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.tailPosition = try container.decode(CGPoint.self, forKey: .tailPosition)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.drawType, forKey: .drawType)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.size, forKey: .size)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.tailPosition, forKey: .tailPosition)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingBubbleEntity(drawType: self.drawType, color: self.color, lineWidth: self.lineWidth)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.size = self.size
newEntity.rotation = self.rotation
return newEntity
}
}

View File

@ -0,0 +1,161 @@
import Foundation
import UIKit
import simd
public struct DrawingColor: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case red
case green
case blue
case alpha
case position
}
public static var clear = DrawingColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
public var red: CGFloat
public var green: CGFloat
public var blue: CGFloat
public var alpha: CGFloat
public var position: CGPoint?
public var isClear: Bool {
return self.red.isZero && self.green.isZero && self.blue.isZero && self.alpha.isZero
}
public init(
red: CGFloat,
green: CGFloat,
blue: CGFloat,
alpha: CGFloat = 1.0,
position: CGPoint? = nil
) {
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
self.position = position
}
public init(color: UIColor) {
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var alpha: CGFloat = 1.0
if color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
self.init(red: red, green: green, blue: blue, alpha: alpha)
} else if color.getWhite(&red, alpha: &alpha) {
self.init(red: red, green: red, blue: red, alpha: alpha)
} else {
self.init(red: 0.0, green: 0.0, blue: 0.0)
}
}
public init(rgb: UInt32) {
self.init(color: UIColor(rgb: rgb))
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.red = try container.decode(CGFloat.self, forKey: .red)
self.green = try container.decode(CGFloat.self, forKey: .green)
self.blue = try container.decode(CGFloat.self, forKey: .blue)
self.alpha = try container.decode(CGFloat.self, forKey: .alpha)
self.position = try container.decodeIfPresent(CGPoint.self, forKey: .position)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.red, forKey: .red)
try container.encode(self.green, forKey: .green)
try container.encode(self.blue, forKey: .blue)
try container.encode(self.alpha, forKey: .alpha)
try container.encodeIfPresent(self.position, forKey: .position)
}
public func withUpdatedRed(_ red: CGFloat) -> DrawingColor {
return DrawingColor(
red: red,
green: self.green,
blue: self.blue,
alpha: self.alpha
)
}
public func withUpdatedGreen(_ green: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: green,
blue: self.blue,
alpha: self.alpha
)
}
public func withUpdatedBlue(_ blue: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: blue,
alpha: self.alpha
)
}
public func withUpdatedAlpha(_ alpha: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: alpha,
position: self.position
)
}
public func withUpdatedPosition(_ position: CGPoint) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: self.alpha,
position: position
)
}
public func toUIColor() -> UIColor {
return UIColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: self.alpha
)
}
public func toCGColor() -> CGColor {
return self.toUIColor().cgColor
}
public func toFloat4() -> vector_float4 {
return [
simd_float1(self.red),
simd_float1(self.green),
simd_float1(self.blue),
simd_float1(self.alpha)
]
}
public static func ==(lhs: DrawingColor, rhs: DrawingColor) -> Bool {
if lhs.red != rhs.red {
return false
}
if lhs.green != rhs.green {
return false
}
if lhs.blue != rhs.blue {
return false
}
if lhs.alpha != rhs.alpha {
return false
}
return true
}
}

View File

@ -0,0 +1,20 @@
import Foundation
import UIKit
public protocol DrawingEntity: AnyObject {
var uuid: UUID { get }
var isAnimated: Bool { get }
var center: CGPoint { get }
var isMedia: Bool { get }
var lineWidth: CGFloat { get set }
var color: DrawingColor { get set }
var scale: CGFloat { get set }
func duplicate() -> DrawingEntity
var renderImage: UIImage? { get set }
var renderSubEntities: [DrawingEntity]? { get set }
}

View File

@ -0,0 +1,146 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import TelegramCore
import AccountContext
import Photos
public final class DrawingMediaEntity: DrawingEntity, Codable {
public enum Content {
case image(UIImage, PixelDimensions)
case video(String, PixelDimensions)
case asset(PHAsset)
var dimensions: PixelDimensions {
switch self {
case let .image(_, dimensions), let .video(_, dimensions):
return dimensions
case let .asset(asset):
return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight))
}
}
}
private enum CodingKeys: String, CodingKey {
case uuid
case image
case videoPath
case assetId
case size
case width
case height
case referenceDrawingSize
case position
case scale
case rotation
case mirrored
}
public let uuid: UUID
public let content: Content
public let size: CGSize
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var scale: CGFloat
public var rotation: CGFloat
public var mirrored: Bool
public var color: DrawingColor = DrawingColor.clear
public var lineWidth: CGFloat = 0.0
public var center: CGPoint {
return self.position
}
public var baseSize: CGSize {
return self.size
}
public var isAnimated: Bool {
switch self.content {
case .image:
return false
case .video:
return true
case let .asset(asset):
return asset.mediaType == .video
}
}
public var isMedia: Bool {
return true
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public init(content: Content, size: CGSize) {
self.uuid = UUID()
self.content = content
self.size = size
self.referenceDrawingSize = .zero
self.position = CGPoint()
self.scale = 1.0
self.rotation = 0.0
self.mirrored = false
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.size = try container.decode(CGSize.self, forKey: .size)
let width = try container.decode(Int32.self, forKey: .width)
let height = try container.decode(Int32.self, forKey: .height)
if let videoPath = try container.decodeIfPresent(String.self, forKey: .videoPath) {
self.content = .video(videoPath, PixelDimensions(width: width, height: height))
} else if let imageData = try container.decodeIfPresent(Data.self, forKey: .image), let image = UIImage(data: imageData) {
self.content = .image(image, PixelDimensions(width: width, height: height))
} else if let _ = try container.decodeIfPresent(String.self, forKey: .assetId) {
fatalError()
//self.content = .asset()
} else {
fatalError()
}
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.mirrored = try container.decode(Bool.self, forKey: .mirrored)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
switch self.content {
case let .video(videoPath, dimensions):
try container.encode(videoPath, forKey: .videoPath)
try container.encode(dimensions.width, forKey: .width)
try container.encode(dimensions.height, forKey: .height)
case let .image(image, dimensions):
try container.encodeIfPresent(image.jpegData(compressionQuality: 0.9), forKey: .image)
try container.encode(dimensions.width, forKey: .width)
try container.encode(dimensions.height, forKey: .height)
case let .asset(asset):
try container.encode(asset.localIdentifier, forKey: .assetId)
}
try container.encode(self.size, forKey: .size)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.mirrored, forKey: .mirrored)
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingMediaEntity(content: self.content, size: self.size)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.scale = self.scale
newEntity.rotation = self.rotation
newEntity.mirrored = self.mirrored
return newEntity
}
}

View File

@ -0,0 +1,113 @@
import Foundation
import UIKit
import Display
import AccountContext
public final class DrawingSimpleShapeEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case shapeType
case drawType
case color
case lineWidth
case referenceDrawingSize
case position
case size
case rotation
case renderImage
}
public enum ShapeType: Codable {
case rectangle
case ellipse
case star
}
public enum DrawType: Codable {
case fill
case stroke
}
public let uuid: UUID
public let isAnimated: Bool
public var shapeType: ShapeType
public var drawType: DrawType
public var color: DrawingColor
public var lineWidth: CGFloat
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var size: CGSize
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(shapeType: ShapeType, drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.shapeType = shapeType
self.drawType = drawType
self.color = color
self.lineWidth = lineWidth
self.referenceDrawingSize = .zero
self.position = .zero
self.size = CGSize(width: 1.0, height: 1.0)
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.shapeType = try container.decode(ShapeType.self, forKey: .shapeType)
self.drawType = try container.decode(DrawType.self, forKey: .drawType)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.size = try container.decode(CGSize.self, forKey: .size)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.shapeType, forKey: .shapeType)
try container.encode(self.drawType, forKey: .drawType)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.size, forKey: .size)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingSimpleShapeEntity(shapeType: self.shapeType, drawType: self.drawType, color: self.color, lineWidth: self.lineWidth)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.size = self.size
newEntity.rotation = self.rotation
return newEntity
}
}

View File

@ -0,0 +1,113 @@
import Foundation
import UIKit
import Display
import AccountContext
import TelegramCore
public final class DrawingStickerEntity: DrawingEntity, Codable {
public enum Content {
case file(TelegramMediaFile)
case image(UIImage)
}
private enum CodingKeys: String, CodingKey {
case uuid
case file
case image
case referenceDrawingSize
case position
case scale
case rotation
case mirrored
}
public let uuid: UUID
public let content: Content
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var scale: CGFloat
public var rotation: CGFloat
public var mirrored: Bool
public var color: DrawingColor = DrawingColor.clear
public var lineWidth: CGFloat = 0.0
public var center: CGPoint {
return self.position
}
public var baseSize: CGSize {
let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.2)
return CGSize(width: size, height: size)
}
public var isAnimated: Bool {
switch self.content {
case let .file(file):
return file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm"
case .image:
return false
}
}
public var isMedia: Bool {
return false
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public init(content: Content) {
self.uuid = UUID()
self.content = content
self.referenceDrawingSize = .zero
self.position = CGPoint()
self.scale = 1.0
self.rotation = 0.0
self.mirrored = false
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) {
self.content = .file(file)
} else if let imageData = try container.decodeIfPresent(Data.self, forKey: .image), let image = UIImage(data: imageData) {
self.content = .image(image)
} else {
fatalError()
}
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.mirrored = try container.decode(Bool.self, forKey: .mirrored)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
switch self.content {
case let .file(file):
try container.encode(file, forKey: .file)
case let .image(image):
try container.encodeIfPresent(image.pngData(), forKey: .image)
}
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.mirrored, forKey: .mirrored)
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingStickerEntity(content: self.content)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.scale = self.scale
newEntity.rotation = self.rotation
newEntity.mirrored = self.mirrored
return newEntity
}
}

View File

@ -0,0 +1,277 @@
import Foundation
import UIKit
import Display
import AccountContext
import TextFormat
public final class DrawingTextEntity: DrawingEntity, Codable {
final class CustomEmojiAttribute: Codable {
private enum CodingKeys: String, CodingKey {
case attribute
case rangeOrigin
case rangeLength
}
let attribute: ChatTextInputTextCustomEmojiAttribute
let range: NSRange
init(attribute: ChatTextInputTextCustomEmojiAttribute, range: NSRange) {
self.attribute = attribute
self.range = range
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.attribute = try container.decode(ChatTextInputTextCustomEmojiAttribute.self, forKey: .attribute)
let rangeOrigin = try container.decode(Int.self, forKey: .rangeOrigin)
let rangeLength = try container.decode(Int.self, forKey: .rangeLength)
self.range = NSMakeRange(rangeOrigin, rangeLength)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.attribute, forKey: .attribute)
try container.encode(self.range.location, forKey: .rangeOrigin)
try container.encode(self.range.length, forKey: .rangeLength)
}
}
private enum CodingKeys: String, CodingKey {
case uuid
case text
case textAttributes
case style
case animation
case font
case alignment
case fontSize
case color
case referenceDrawingSize
case position
case width
case scale
case rotation
case renderImage
case renderSubEntities
case renderAnimationFrames
}
public enum Style: Codable {
case regular
case filled
case semi
case stroke
}
public enum Animation: Codable {
case none
case typing
case wiggle
case zoomIn
}
public enum Font: Codable {
case sanFrancisco
case other(String, String)
}
public enum Alignment: Codable {
case left
case center
case right
}
public var uuid: UUID
public var isAnimated: Bool {
if self.animation != .none {
return true
}
var isAnimated = false
self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in
if let _ = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
isAnimated = true
}
})
return isAnimated
}
public var text: NSAttributedString
public var style: Style
public var animation: Animation
public var font: Font
public var alignment: Alignment
public var fontSize: CGFloat
public var color: DrawingColor
public var lineWidth: CGFloat = 0.0
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var width: CGFloat
public var scale: CGFloat
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public class AnimationFrame: Codable {
private enum CodingKeys: String, CodingKey {
case timestamp
case duration
case image
}
public let timestamp: Double
public let duration: Double
public let image: UIImage
public init(timestamp: Double, duration: Double, image: UIImage) {
self.timestamp = timestamp
self.duration = duration
self.image = image
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.timestamp = try container.decode(Double.self, forKey: .timestamp)
self.duration = try container.decode(Double.self, forKey: .duration)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .image) {
self.image = UIImage(data: renderImageData)!
} else {
fatalError()
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.timestamp, forKey: .timestamp)
try container.encode(self.duration, forKey: .duration)
if let data = self.image.pngData() {
try container.encode(data, forKey: .image)
}
}
}
public var renderAnimationFrames: [AnimationFrame]?
public init(text: NSAttributedString, style: Style, animation: Animation, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) {
self.uuid = UUID()
self.text = text
self.style = style
self.animation = animation
self.font = font
self.alignment = alignment
self.fontSize = fontSize
self.color = color
self.referenceDrawingSize = .zero
self.position = .zero
self.width = 100.0
self.scale = 1.0
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
let text = try container.decode(String.self, forKey: .text)
let attributedString = NSMutableAttributedString(string: text)
let textAttributes = try container.decode([CustomEmojiAttribute].self, forKey: .textAttributes)
for attribute in textAttributes {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: attribute.attribute, range: attribute.range)
}
self.text = attributedString
self.style = try container.decode(Style.self, forKey: .style)
self.animation = try container.decode(Animation.self, forKey: .animation)
self.font = try container.decode(Font.self, forKey: .font)
self.alignment = try container.decode(Alignment.self, forKey: .alignment)
self.fontSize = try container.decode(CGFloat.self, forKey: .fontSize)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.width = try container.decode(CGFloat.self, forKey: .width)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
if let renderSubEntities = try? container.decodeIfPresent([CodableDrawingEntity].self, forKey: .renderSubEntities) {
self.renderSubEntities = renderSubEntities.compactMap { $0.entity as? DrawingStickerEntity }
}
self.renderAnimationFrames = try container.decodeIfPresent([AnimationFrame].self, forKey: .renderAnimationFrames)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.text.string, forKey: .text)
var textAttributes: [CustomEmojiAttribute] = []
self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in
if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
textAttributes.append(CustomEmojiAttribute(attribute: value, range: range))
}
})
try container.encode(textAttributes, forKey: .textAttributes)
try container.encode(self.style, forKey: .style)
try container.encode(self.animation, forKey: .animation)
try container.encode(self.font, forKey: .font)
try container.encode(self.alignment, forKey: .alignment)
try container.encode(self.fontSize, forKey: .fontSize)
try container.encode(self.color, forKey: .color)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.width, forKey: .width)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
if let renderSubEntities = self.renderSubEntities {
let codableEntities: [CodableDrawingEntity] = renderSubEntities.compactMap { CodableDrawingEntity(entity: $0) }
try container.encode(codableEntities, forKey: .renderSubEntities)
}
if let renderAnimationFrames = self.renderAnimationFrames {
try container.encode(renderAnimationFrames, forKey: .renderAnimationFrames)
}
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingTextEntity(text: self.text, style: self.style, animation: self.animation, font: self.font, alignment: self.alignment, fontSize: self.fontSize, color: self.color)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.width = self.width
newEntity.scale = self.scale
newEntity.rotation = self.rotation
return newEntity
}
// public weak var currentEntityView: DrawingEntityView?
// public func makeView(context: AccountContext) -> DrawingEntityView {
// let entityView = DrawingTextEntityView(context: context, entity: self)
// self.currentEntityView = entityView
// return entityView
// }
//
// public func prepareForRender() {
// self.renderImage = (self.currentEntityView as? DrawingTextEntityView)?.getRenderImage()
// self.renderSubEntities = (self.currentEntityView as? DrawingTextEntityView)?.getRenderSubEntities()
//
// if case .none = self.animation {
// self.renderAnimationFrames = nil
// } else {
// self.renderAnimationFrames = (self.currentEntityView as? DrawingTextEntityView)?.getRenderAnimationFrames()
// }
// }
}

View File

@ -0,0 +1,110 @@
import Foundation
import UIKit
import Display
import AccountContext
public final class DrawingVectorEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case type
case color
case lineWidth
case drawingSize
case referenceDrawingSize
case start
case mid
case end
case renderImage
}
public enum VectorType: Codable {
case line
case oneSidedArrow
case twoSidedArrow
}
public let uuid: UUID
public let isAnimated: Bool
public var type: VectorType
public var color: DrawingColor
public var lineWidth: CGFloat
public var drawingSize: CGSize
public var referenceDrawingSize: CGSize
public var start: CGPoint
public var mid: (CGFloat, CGFloat)
public var end: CGPoint
public var center: CGPoint {
return self.start
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(type: VectorType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.type = type
self.color = color
self.lineWidth = lineWidth
self.drawingSize = .zero
self.referenceDrawingSize = .zero
self.start = CGPoint()
self.mid = (0.5, 0.0)
self.end = CGPoint()
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.type = try container.decode(VectorType.self, forKey: .type)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.start = try container.decode(CGPoint.self, forKey: .start)
let mid = try container.decode(CGPoint.self, forKey: .mid)
self.mid = (mid.x, mid.y)
self.end = try container.decode(CGPoint.self, forKey: .end)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.type, forKey: .type)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.drawingSize, forKey: .drawingSize)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.start, forKey: .start)
try container.encode(CGPoint(x: self.mid.0, y: self.mid.1), forKey: .mid)
try container.encode(self.end, forKey: .end)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate() -> DrawingEntity {
let newEntity = DrawingVectorEntity(type: self.type, color: self.color, lineWidth: self.lineWidth)
newEntity.drawingSize = self.drawingSize
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.start = self.start
newEntity.mid = self.mid
newEntity.end = self.end
return newEntity
}
}

View File

@ -89,6 +89,7 @@ public final class MediaEditor {
videoTrimRange: nil, videoTrimRange: nil,
videoIsMuted: false, videoIsMuted: false,
drawing: nil, drawing: nil,
entities: [],
toolValues: [:] toolValues: [:]
) )
} }
@ -267,6 +268,10 @@ public final class MediaEditor {
self.values = self.values.withUpdatedVideoIsMuted(videoIsMuted) self.values = self.values.withUpdatedVideoIsMuted(videoIsMuted)
} }
public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) {
self.values = self.values.withUpdatedDrawingAndEntities(drawing: image, entities: entities)
}
public func setGradientColors(_ gradientColors: [UIColor]) { public func setGradientColors(_ gradientColors: [UIColor]) {
self.values = self.values.withUpdatedGradientColors(gradientColors: gradientColors) self.values = self.values.withUpdatedGradientColors(gradientColors: gradientColors)
} }

View File

@ -4,6 +4,13 @@ import UIKit
import CoreImage import CoreImage
import Metal import Metal
import Display import Display
import SwiftSignalKit
import TelegramCore
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import YuvConversion
import StickerResources
import AccountContext
final class MediaEditorComposer { final class MediaEditorComposer {
let device: MTLDevice? let device: MTLDevice?
@ -17,15 +24,14 @@ final class MediaEditorComposer {
private let renderer = MediaEditorRenderer() private let renderer = MediaEditorRenderer()
private let renderChain = MediaEditorRenderChain() private let renderChain = MediaEditorRenderChain()
private var gradientImage: CIImage private let gradientImage: CIImage
private let drawingImage: CIImage?
let semaphore = DispatchSemaphore(value: 1) private var entities: [MediaEditorComposerEntity]
init(values: MediaEditorValues, dimensions: CGSize) { init(context: AccountContext, values: MediaEditorValues, dimensions: CGSize) {
self.values = values self.values = values
self.dimensions = dimensions self.dimensions = dimensions
self.renderer.externalSemaphore = self.semaphore
self.renderer.addRenderChain(self.renderChain) self.renderer.addRenderChain(self.renderChain)
self.renderer.addRenderPass(ComposerRenderPass()) self.renderer.addRenderPass(ComposerRenderPass())
@ -36,6 +42,14 @@ final class MediaEditorComposer {
self.gradientImage = CIImage(color: .black) self.gradientImage = CIImage(color: .black)
} }
if let drawing = values.drawing, let drawingImage = CIImage(image: drawing) {
self.drawingImage = drawingImage.transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
} else {
self.drawingImage = nil
}
self.entities = values.entities.map { $0.entity } .compactMap { composerEntityForDrawingEntity(context: context, entity: $0) }
self.device = MTLCreateSystemDefaultDevice() self.device = MTLCreateSystemDefaultDevice()
if let device = self.device { if let device = self.device {
self.ciContext = CIContext(mtlDevice: device, options: [.workingColorSpace : NSNull()]) self.ciContext = CIContext(mtlDevice: device, options: [.workingColorSpace : NSNull()])
@ -49,10 +63,13 @@ final class MediaEditorComposer {
self.renderChain.update(values: self.values) self.renderChain.update(values: self.values)
} }
func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, pool: CVPixelBufferPool?) -> CVPixelBuffer? { func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, pool: CVPixelBufferPool?, completion: @escaping (CVPixelBuffer?) -> Void) {
guard let textureCache = self.textureCache, let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), let pool = pool else { guard let textureCache = self.textureCache, let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), let pool = pool else {
return nil completion(nil)
return
} }
let time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
let width = CVPixelBufferGetWidth(imageBuffer) let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer) let height = CVPixelBufferGetHeight(imageBuffer)
let format: MTLPixelFormat = .bgra8Unorm let format: MTLPixelFormat = .bgra8Unorm
@ -62,7 +79,6 @@ final class MediaEditorComposer {
if status == kCVReturnSuccess { if status == kCVReturnSuccess {
texture = CVMetalTextureGetTexture(textureRef!) texture = CVMetalTextureGetTexture(textureRef!)
} }
if let texture { if let texture {
self.renderer.consumeTexture(texture, rotation: .rotate90Degrees) self.renderer.consumeTexture(texture, rotation: .rotate90Degrees)
self.renderer.renderFrame() self.renderer.renderFrame()
@ -73,45 +89,59 @@ final class MediaEditorComposer {
var pixelBuffer: CVPixelBuffer? var pixelBuffer: CVPixelBuffer?
CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer) CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer)
if let composition = processImage(inputImage: ciImage), let pixelBuffer { if let pixelBuffer {
self.ciContext?.render(composition, to: pixelBuffer) processImage(inputImage: ciImage, time: time, completion: { compositedImage in
if let compositedImage {
return pixelBuffer self.ciContext?.render(compositedImage, to: pixelBuffer)
} else { completion(pixelBuffer)
return nil } else {
completion(nil)
}
})
return
} }
} else {
return nil
} }
} else {
return nil
} }
completion(nil)
return
} }
func processImage(inputImage: CIImage) -> CIImage? { func processImage(inputImage: CIImage, time: CMTime, completion: @escaping (CIImage?) -> Void) {
return makeEditorImageFrameComposition(inputImage: inputImage, gradientImage: self.gradientImage, dimensions: self.dimensions, values: self.values) return makeEditorImageFrameComposition(inputImage: inputImage, gradientImage: self.gradientImage, drawingImage: self.drawingImage, dimensions: self.dimensions, values: self.values, entities: self.entities, time: time, completion: completion)
} }
} }
public func makeEditorImageComposition(inputImage: UIImage, dimensions: CGSize, values: MediaEditorValues) -> UIImage? { public func makeEditorImageComposition(context: AccountContext, inputImage: UIImage, dimensions: CGSize, values: MediaEditorValues, time: CMTime, completion: @escaping (UIImage?) -> Void) {
let inputImage = CIImage(image: inputImage)! let inputImage = CIImage(image: inputImage)!
let gradientImage: CIImage let gradientImage: CIImage
var drawingImage: CIImage?
if let gradientColors = values.gradientColors { if let gradientColors = values.gradientColors {
let image = generateGradientImage(size: dimensions, scale: 1.0, colors: gradientColors, locations: [0.0, 1.0])! let image = generateGradientImage(size: dimensions, scale: 1.0, colors: gradientColors, locations: [0.0, 1.0])!
gradientImage = CIImage(image: image)!.transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0)) gradientImage = CIImage(image: image)!.transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
} else { } else {
gradientImage = CIImage(color: .black) gradientImage = CIImage(color: .black)
} }
if let ciImage = makeEditorImageFrameComposition(inputImage: inputImage, gradientImage: gradientImage, dimensions: dimensions, values: values) {
let context = CIContext(options: [.workingColorSpace : NSNull()]) if let drawing = values.drawing, let image = CIImage(image: drawing) {
if let cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: ciImage.extent.size)) { drawingImage = image.transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
return UIImage(cgImage: cgImage)
}
} }
return nil
let entities: [MediaEditorComposerEntity] = values.entities.map { $0.entity }.compactMap { composerEntityForDrawingEntity(context: context, entity: $0) }
makeEditorImageFrameComposition(inputImage: inputImage, gradientImage: gradientImage, drawingImage: drawingImage, dimensions: dimensions, values: values, entities: entities, time: time, completion: { ciImage in
if let ciImage {
let context = CIContext(options: [.workingColorSpace : NSNull()])
if let cgImage = context.createCGImage(ciImage, from: CGRect(origin: .zero, size: ciImage.extent.size)) {
Queue.mainQueue().async {
completion(UIImage(cgImage: cgImage))
}
return
}
}
completion(nil)
})
} }
private func makeEditorImageFrameComposition(inputImage: CIImage, gradientImage: CIImage, dimensions: CGSize, values: MediaEditorValues) -> CIImage? { private func makeEditorImageFrameComposition(inputImage: CIImage, gradientImage: CIImage, drawingImage: CIImage?, dimensions: CGSize, values: MediaEditorValues, entities: [MediaEditorComposerEntity], time: CMTime, completion: @escaping (CIImage?) -> Void) {
var resultImage = CIImage(color: .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0)) var resultImage = CIImage(color: .black).cropped(to: CGRect(origin: .zero, size: dimensions)).transformed(by: CGAffineTransform(translationX: -dimensions.width / 2.0, y: -dimensions.height / 2.0))
resultImage = gradientImage.composited(over: resultImage) resultImage = gradientImage.composited(over: resultImage)
@ -121,32 +151,377 @@ private func makeEditorImageFrameComposition(inputImage: CIImage, gradientImage:
cropTransform = cropTransform.rotated(by: -values.cropRotation) cropTransform = cropTransform.rotated(by: -values.cropRotation)
cropTransform = cropTransform.scaledBy(x: values.cropScale, y: values.cropScale) cropTransform = cropTransform.scaledBy(x: values.cropScale, y: values.cropScale)
mediaImage = mediaImage.transformed(by: cropTransform) mediaImage = mediaImage.transformed(by: cropTransform)
resultImage = mediaImage.composited(over: resultImage) resultImage = mediaImage.composited(over: resultImage)
return resultImage.transformed(by: CGAffineTransform(translationX: dimensions.width / 2.0, y: dimensions.height / 2.0)) if let drawingImage {
resultImage = drawingImage.composited(over: resultImage)
}
let frameRate: Float = 60.0
let entitiesCount = Atomic<Int>(value: 1)
let entitiesImages = Atomic<[(CIImage, Int)]>(value: [])
let maybeFinalize = {
let count = entitiesCount.modify { current -> Int in
return current - 1
}
if count == 0 {
let sortedImages = entitiesImages.with({ $0 }).sorted(by: { $0.1 < $1.1 }).map({ $0.0 })
for image in sortedImages {
resultImage = image.composited(over: resultImage)
}
resultImage = resultImage.transformed(by: CGAffineTransform(translationX: dimensions.width / 2.0, y: dimensions.height / 2.0))
completion(resultImage)
}
}
var i = 0
for entity in entities {
let _ = entitiesCount.modify { current -> Int in
return current + 1
}
let index = i
entity.image(for: time, frameRate: frameRate, completion: { image in
if var image = image {
var transform = CGAffineTransform(translationX: -image.extent.midX, y: -image.extent.midY)
image = image.transformed(by: transform)
var scale = entity.scale * 1.0
if let baseSize = entity.baseSize {
scale *= baseSize.width / image.extent.size.width
}
transform = CGAffineTransform(translationX: entity.position.x, y: dimensions.height - entity.position.y)
transform = transform.rotated(by: CGFloat.pi * 2.0 - entity.rotation)
transform = transform.scaledBy(x: scale, y: scale)
if entity.mirrored {
transform = transform.scaledBy(x: -1.0, y: 1.0)
}
image = image.transformed(by: transform)
let _ = entitiesImages.modify { current in
var updated = current
updated.append((image, index))
return updated
}
}
maybeFinalize()
})
i += 1
}
maybeFinalize()
} }
extension CMSampleBuffer { private func composerEntityForDrawingEntity(context: AccountContext, entity: DrawingEntity) -> MediaEditorComposerEntity? {
func newSampleBufferWithReplacedImageBuffer(_ imageBuffer: CVImageBuffer) -> CMSampleBuffer? { if let entity = entity as? DrawingStickerEntity {
guard let _ = CMSampleBufferGetImageBuffer(self) else { let content: MediaEditorComposerStickerEntity.Content
return nil switch entity.content {
case let .file(file):
content = .file(file)
case let .image(image):
content = .image(image)
} }
var timingInfo = CMSampleTimingInfo() return MediaEditorComposerStickerEntity(context: context, content: content, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored)
guard CMSampleBufferGetSampleTimingInfo(self, at: 0, timingInfoOut: &timingInfo) == 0 else { } else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage) {
return nil if let entity = entity as? DrawingBubbleEntity {
return MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, mirrored: false)
} else if let entity = entity as? DrawingSimpleShapeEntity {
return MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, mirrored: false)
} else if let entity = entity as? DrawingVectorEntity {
return MediaEditorComposerStaticEntity(image: image, position: CGPoint(x: entity.drawingSize.width * 0.5, y: entity.drawingSize.height * 0.5), scale: 1.0, rotation: 0.0, baseSize: entity.drawingSize, mirrored: false)
} else if let entity = entity as? DrawingTextEntity {
return MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: nil, mirrored: false)
} }
var outputSampleBuffer: CMSampleBuffer? }
var newFormatDescription: CMFormatDescription? return nil
CMVideoFormatDescriptionCreateForImageBuffer(allocator: nil, imageBuffer: imageBuffer, formatDescriptionOut: &newFormatDescription) }
guard let formatDescription = newFormatDescription else {
return nil private class MediaEditorComposerStaticEntity: MediaEditorComposerEntity {
} let image: CIImage
CMSampleBufferCreateReadyWithImageBuffer(allocator: nil, imageBuffer: imageBuffer, formatDescription: formatDescription, sampleTiming: &timingInfo, sampleBufferOut: &outputSampleBuffer) let position: CGPoint
return outputSampleBuffer let scale: CGFloat
let rotation: CGFloat
let baseSize: CGSize?
let mirrored: Bool
init(image: CIImage, position: CGPoint, scale: CGFloat, rotation: CGFloat, baseSize: CGSize?, mirrored: Bool) {
self.image = image
self.position = position
self.scale = scale
self.rotation = rotation
self.baseSize = baseSize
self.mirrored = mirrored
}
func image(for time: CMTime, frameRate: Float, completion: @escaping (CIImage?) -> Void) {
completion(self.image)
} }
} }
private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
public enum Content {
case file(TelegramMediaFile)
case image(UIImage)
var file: TelegramMediaFile? {
if case let .file(file) = self {
return file
}
return nil
}
}
let content: Content
let position: CGPoint
let scale: CGFloat
let rotation: CGFloat
let baseSize: CGSize?
let mirrored: Bool
var isAnimated: Bool
var source: AnimatedStickerNodeSource?
var frameSource = Promise<QueueLocalObject<AnimatedStickerDirectFrameSource>?>()
var frameCount: Int?
var frameRate: Int?
var currentFrameIndex: Int?
var totalDuration: Double?
let durationPromise = Promise<Double>()
let queue = Queue()
let disposables = DisposableSet()
var image: CIImage?
var imagePixelBuffer: CVPixelBuffer?
let imagePromise = Promise<UIImage>()
init(context: AccountContext, content: Content, position: CGPoint, scale: CGFloat, rotation: CGFloat, baseSize: CGSize, mirrored: Bool) {
self.content = content
self.position = position
self.scale = scale
self.rotation = rotation
self.baseSize = baseSize
self.mirrored = mirrored
switch content {
case let .file(file):
if file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" {
self.isAnimated = true
self.source = AnimatedStickerResourceSource(account: context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm")
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
if let source = self.source {
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384, height: 384))
self.disposables.add((source.directDataPath(attemptSynchronously: true)
|> deliverOn(self.queue)).start(next: { [weak self] path in
if let strongSelf = self, let path {
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
let queue = strongSelf.queue
let frameSource = QueueLocalObject<AnimatedStickerDirectFrameSource>(queue: queue, generate: {
return AnimatedStickerDirectFrameSource(queue: queue, data: data, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), cachePathPrefix: pathPrefix, useMetalCache: false, fitzModifier: nil)!
//return AnimatedStickerCachedFrameSource(queue: queue, data: data, complete: complete, notifyUpdated: {})!
})
frameSource.syncWith { frameSource in
strongSelf.frameCount = frameSource.frameCount
strongSelf.frameRate = frameSource.frameRate
let duration = Double(frameSource.frameCount) / Double(frameSource.frameRate)
strongSelf.totalDuration = duration
strongSelf.durationPromise.set(.single(duration))
}
strongSelf.frameSource.set(.single(frameSource))
}
}
}))
}
} else {
self.isAnimated = false
self.disposables.add((chatMessageSticker(account: context.account, userLocation: .other, file: 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: baseSize, boundingSize: baseSize, intrinsicInsets: UIEdgeInsets()))
let image = context?.generateImage()
if let image = image {
strongSelf.imagePromise.set(.single(image))
}
}
}))
}
case let .image(image):
self.isAnimated = false
self.imagePromise.set(.single(image))
}
}
deinit {
self.disposables.dispose()
}
var tested = false
func image(for time: CMTime, frameRate: Float, completion: @escaping (CIImage?) -> Void) {
if self.isAnimated {
let currentTime = CMTimeGetSeconds(time)
var tintColor: UIColor?
if let file = self.content.file, file.isCustomTemplateEmoji {
tintColor = .white
}
// let start = CACurrentMediaTime()
self.disposables.add((self.frameSource.get()
|> take(1)
|> deliverOn(self.queue)).start(next: { [weak self] frameSource in
guard let strongSelf = self else {
completion(nil)
return
}
guard let frameSource, let duration = strongSelf.totalDuration, let frameCount = strongSelf.frameCount else {
completion(nil)
return
}
// if !strongSelf.tested {
// frameSource.syncWith { frameSource in
// for _ in 0 ..< 60 * 3 {
// let _ = frameSource.takeFrame(draw: true)
// }
// }
// strongSelf.tested = true
// print("180 frames in \(CACurrentMediaTime() - start)")
// }
let relativeTime = currentTime - floor(currentTime / duration) * duration
var t = relativeTime / duration
t = max(0.0, t)
t = min(1.0, t)
let startFrame: Double = 0
let endFrame = Double(frameCount)
let frameOffset = Int(Double(startFrame) * (1.0 - t) + Double(endFrame - 1) * t)
let lowerBound: Int = 0
let upperBound = frameCount - 1
let frameIndex = max(lowerBound, min(upperBound, frameOffset))
let currentFrameIndex = strongSelf.currentFrameIndex
if currentFrameIndex != frameIndex {
let previousFrameIndex = currentFrameIndex
strongSelf.currentFrameIndex = frameIndex
var delta = 1
if let previousFrameIndex = previousFrameIndex {
delta = max(1, frameIndex - previousFrameIndex)
}
//print("skipping: \(delta) frames")
var frame: AnimatedStickerFrame?
frameSource.syncWith { frameSource in
for i in 0 ..< delta {
frame = frameSource.takeFrame(draw: i == delta - 1)
}
}
if let frame {
//print("has frame: \(CACurrentMediaTime() - start)")
var imagePixelBuffer: CVPixelBuffer?
if let pixelBuffer = strongSelf.imagePixelBuffer {
imagePixelBuffer = pixelBuffer
} else {
let ioSurfaceProperties = NSMutableDictionary()
let options = NSMutableDictionary()
options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString)
var pixelBuffer: CVPixelBuffer?
CVPixelBufferCreate(
kCFAllocatorDefault,
frame.width,
frame.height,
kCVPixelFormatType_32BGRA,
options,
&pixelBuffer
)
imagePixelBuffer = pixelBuffer
strongSelf.imagePixelBuffer = pixelBuffer
}
if let imagePixelBuffer {
let image = render(width: frame.width, height: frame.height, bytesPerRow: frame.bytesPerRow, data: frame.data, type: frame.type, pixelBuffer: imagePixelBuffer, tintColor: tintColor)
//print("image loaded in: \(CACurrentMediaTime() - start)")
strongSelf.image = image
}
completion(strongSelf.image)
} else {
completion(nil)
}
} else {
completion(strongSelf.image)
}
}))
} else {
var image: CIImage?
if let cachedImage = self.image {
image = cachedImage
completion(image)
} else {
let _ = (self.imagePromise.get()
|> take(1)
|> deliverOn(self.queue)).start(next: { [weak self] image in
if let strongSelf = self {
strongSelf.image = CIImage(image: image)
completion(strongSelf.image)
}
})
}
}
}
}
protocol MediaEditorComposerEntity {
var position: CGPoint { get }
var scale: CGFloat { get }
var rotation: CGFloat { get }
var baseSize: CGSize? { get }
var mirrored: Bool { get }
func image(for time: CMTime, frameRate: Float, completion: @escaping (CIImage?) -> Void)
}
private func render(width: Int, height: Int, bytesPerRow: Int, data: Data, type: AnimationRendererFrameType, pixelBuffer: CVPixelBuffer, tintColor: UIColor?) -> CIImage? {
//let calculatedBytesPerRow = (4 * Int(width) + 31) & (~31)
//assert(bytesPerRow == calculatedBytesPerRow)
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
let dest = CVPixelBufferGetBaseAddress(pixelBuffer)
switch type {
case .yuva:
data.withUnsafeBytes { buffer -> Void in
guard let bytes = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
decodeYUVAToRGBA(bytes, dest, Int32(width), Int32(height), Int32(bytesPerRow))
}
case .argb:
data.withUnsafeBytes { buffer -> Void in
guard let bytes = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
memcpy(dest, bytes, data.count)
}
case .dct:
break
}
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
return CIImage(cvPixelBuffer: pixelBuffer)
}
final class ComposerRenderPass: DefaultRenderPass { final class ComposerRenderPass: DefaultRenderPass {
fileprivate var cachedTexture: MTLTexture? fileprivate var cachedTexture: MTLTexture?

View File

@ -65,9 +65,7 @@ final class MediaEditorRenderer: TextureConsumer {
private var library: MTLLibrary? private var library: MTLLibrary?
var finalTexture: MTLTexture? var finalTexture: MTLTexture?
var externalSemaphore: DispatchSemaphore?
public init() { public init() {
} }
@ -175,7 +173,6 @@ final class MediaEditorRenderer: TextureConsumer {
commandBuffer.addCompletedHandler { [weak self] _ in commandBuffer.addCompletedHandler { [weak self] _ in
self?.semaphore.signal() self?.semaphore.signal()
self?.externalSemaphore?.signal()
} }
if let _ = self.renderTarget { if let _ = self.renderTarget {

View File

@ -34,6 +34,72 @@ private let adjustmentToolsKeys: [EditorToolKey] = [
.sharpen .sharpen
] ]
public class MediaEditorValues {
public let originalDimensions: PixelDimensions
public let cropOffset: CGPoint
public let cropSize: CGSize?
public let cropScale: CGFloat
public let cropRotation: CGFloat
public let cropMirroring: Bool
public let gradientColors: [UIColor]?
public let videoTrimRange: Range<Double>?
public let videoIsMuted: Bool
public let drawing: UIImage?
public let entities: [CodableDrawingEntity]
public let toolValues: [EditorToolKey: Any]
init(
originalDimensions: PixelDimensions,
cropOffset: CGPoint,
cropSize: CGSize?,
cropScale: CGFloat,
cropRotation: CGFloat,
cropMirroring: Bool,
gradientColors: [UIColor]?,
videoTrimRange: Range<Double>?,
videoIsMuted: Bool,
drawing: UIImage?,
entities: [CodableDrawingEntity],
toolValues: [EditorToolKey: Any]
) {
self.originalDimensions = originalDimensions
self.cropOffset = cropOffset
self.cropSize = cropSize
self.cropScale = cropScale
self.cropRotation = cropRotation
self.cropMirroring = cropMirroring
self.gradientColors = gradientColors
self.videoTrimRange = videoTrimRange
self.videoIsMuted = videoIsMuted
self.drawing = drawing
self.entities = entities
self.toolValues = toolValues
}
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: self.toolValues)
}
func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: drawing, entities: entities, toolValues: self.toolValues)
}
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, entities: self.entities, toolValues: toolValues)
}
}
public struct TintValue: Equatable { public struct TintValue: Equatable {
public static let initial = TintValue( public static let initial = TintValue(
color: .clear, color: .clear,
@ -300,64 +366,6 @@ public struct CurvesValue: Equatable {
} }
} }
public class MediaEditorValues {
public let originalDimensions: PixelDimensions
public let cropOffset: CGPoint
public let cropSize: CGSize?
public let cropScale: CGFloat
public let cropRotation: CGFloat
public let cropMirroring: Bool
public let gradientColors: [UIColor]?
public let videoTrimRange: Range<Double>?
public let videoIsMuted: Bool
public let drawing: UIImage?
public let toolValues: [EditorToolKey: Any]
init(
originalDimensions: PixelDimensions,
cropOffset: CGPoint,
cropSize: CGSize?,
cropScale: CGFloat,
cropRotation: CGFloat,
cropMirroring: Bool,
gradientColors: [UIColor]?,
videoTrimRange: Range<Double>?,
videoIsMuted: Bool,
drawing: UIImage?,
toolValues: [EditorToolKey: Any]
) {
self.originalDimensions = originalDimensions
self.cropOffset = cropOffset
self.cropSize = cropSize
self.cropScale = cropScale
self.cropRotation = cropRotation
self.cropMirroring = cropMirroring
self.gradientColors = gradientColors
self.videoTrimRange = videoTrimRange
self.videoIsMuted = videoIsMuted
self.drawing = drawing
self.toolValues = toolValues
}
func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: offset, cropSize: self.cropSize, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, toolValues: self.toolValues)
}
func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, toolValues: self.toolValues)
}
func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, drawing: self.drawing, toolValues: self.toolValues)
}
func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues {
return MediaEditorValues(originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropSize: self.cropSize, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, drawing: self.drawing, toolValues: toolValues)
}
}
private let toolEpsilon: Float = 0.005 private let toolEpsilon: Float = 0.005
public extension MediaEditorValues { public extension MediaEditorValues {

View File

@ -2,6 +2,7 @@ import Foundation
import AVFoundation import AVFoundation
import MetalKit import MetalKit
import SwiftSignalKit import SwiftSignalKit
import AccountContext
enum ExportWriterStatus { enum ExportWriterStatus {
case unknown case unknown
@ -239,6 +240,7 @@ public final class MediaEditorVideoExport {
public private(set) var internalStatus: Status = .idle public private(set) var internalStatus: Status = .idle
private let context: AccountContext
private let subject: Subject private let subject: Subject
private let configuration: Configuration private let configuration: Configuration
private let outputPath: String private let outputPath: String
@ -262,7 +264,10 @@ public final class MediaEditorVideoExport {
private var startTimestamp = CACurrentMediaTime() private var startTimestamp = CACurrentMediaTime()
public init(subject: Subject, configuration: Configuration, outputPath: String) { private let semaphore = DispatchSemaphore(value: 0)
public init(context: AccountContext, subject: Subject, configuration: Configuration, outputPath: String) {
self.context = context
self.subject = subject self.subject = subject
self.configuration = configuration self.configuration = configuration
self.outputPath = outputPath self.outputPath = outputPath
@ -284,7 +289,7 @@ public final class MediaEditorVideoExport {
} }
if self.configuration.values.requiresComposing { if self.configuration.values.requiresComposing {
self.composer = MediaEditorComposer(values: self.configuration.values, dimensions: self.configuration.dimensions) self.composer = MediaEditorComposer(context: self.context, values: self.configuration.values, dimensions: self.configuration.dimensions)
} }
self.setupVideoInput() self.setupVideoInput()
} }
@ -336,7 +341,7 @@ public final class MediaEditorVideoExport {
} }
let audioTracks = asset.tracks(withMediaType: .audio) let audioTracks = asset.tracks(withMediaType: .audio)
if audioTracks.count > 0 { if audioTracks.count > 0, !self.configuration.values.videoIsMuted {
let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil) let audioOutput = AVAssetReaderAudioMixOutput(audioTracks: audioTracks, audioSettings: nil)
audioOutput.alwaysCopiesSampleData = false audioOutput.alwaysCopiesSampleData = false
if reader.canAdd(audioOutput) { if reader.canAdd(audioOutput) {
@ -420,28 +425,40 @@ public final class MediaEditorVideoExport {
return false return false
} }
var appendFailed = false
while writer.isReadyForMoreVideoData { while writer.isReadyForMoreVideoData {
let _ = self.composer?.semaphore.wait(timeout: .distantFuture) if appendFailed {
return false
}
if reader.status != .reading || writer.status != .writing { if reader.status != .reading || writer.status != .writing {
writer.markVideoAsFinished() writer.markVideoAsFinished()
return false return false
} }
self.pauseDispatchGroup.wait() self.pauseDispatchGroup.wait()
if let buffer = output.copyNextSampleBuffer() { if let buffer = output.copyNextSampleBuffer() {
if let pixelBuffer = self.composer?.processSampleBuffer(buffer, pool: writer.pixelBufferPool) { if let composer = self.composer {
let timestamp = CMSampleBufferGetPresentationTimeStamp(buffer) let timestamp = CMSampleBufferGetPresentationTimeStamp(buffer)
if !writer.appendPixelBuffer(pixelBuffer, at: timestamp) { composer.processSampleBuffer(buffer, pool: writer.pixelBufferPool, completion: { pixelBuffer in
writer.markVideoAsFinished() if let pixelBuffer {
return false if !writer.appendPixelBuffer(pixelBuffer, at: timestamp) {
} writer.markVideoAsFinished()
appendFailed = true
}
} else {
if !writer.appendVideoBuffer(buffer) {
writer.markVideoAsFinished()
appendFailed = true
}
}
self.semaphore.signal()
})
self.semaphore.wait()
} else { } else {
if !writer.appendVideoBuffer(buffer) { if !writer.appendVideoBuffer(buffer) {
writer.markVideoAsFinished() writer.markVideoAsFinished()
return false return false
} }
} }
// let progress = (CMSampleBufferGetPresentationTimeStamp(buffer) - self.configuration.timeRange.start).seconds/self.duration.seconds // let progress = (CMSampleBufferGetPresentationTimeStamp(buffer) - self.configuration.timeRange.start).seconds/self.duration.seconds
// if self.videoOutput === output { // if self.videoOutput === output {
// self.dispatchProgressCallback { $0.updateVideoEncodingProgress(fractionCompleted: progress) } // self.dispatchProgressCallback { $0.updateVideoEncodingProgress(fractionCompleted: progress) }

View File

@ -1044,6 +1044,12 @@ public final class MediaEditorScreen: ViewController {
self?.drawingView.isUserInteractionEnabled = false self?.drawingView.isUserInteractionEnabled = false
self?.animateInFromTool() self?.animateInFromTool()
if let result = controller?.generateDrawingResultData() {
self?.mediaEditor?.setDrawingAndEntities(data: result.data, image: result.drawingImage, entities: result.entities)
} else {
self?.mediaEditor?.setDrawingAndEntities(data: nil, image: nil, entities: [])
}
selectionContainerView?.removeFromSuperview() selectionContainerView?.removeFromSuperview()
} }
self.controller?.present(controller, in: .current) self.controller?.present(controller, in: .current)
@ -1217,13 +1223,15 @@ public final class MediaEditorScreen: ViewController {
}) })
} else { } else {
if let image = mediaEditor.resultImage { if let image = mediaEditor.resultImage {
if let resultImage = makeEditorImageComposition(inputImage: image, dimensions: storyDimensions, values: mediaEditor.values) { makeEditorImageComposition(context: self.context, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { resultImage in
self.completion(.image(resultImage, nil), { [weak self] in if let resultImage {
self?.node.animateOut(completion: { [weak self] in self.completion(.image(resultImage, nil), { [weak self] in
self?.dismiss() self?.node.animateOut(completion: { [weak self] in
self?.dismiss()
})
}) })
}) }
} })
} }
} }
} }
@ -1267,7 +1275,7 @@ public final class MediaEditorScreen: ViewController {
let configuration = recommendedExportConfiguration(mediaEditor: mediaEditor) let configuration = recommendedExportConfiguration(mediaEditor: mediaEditor)
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4" let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).mp4"
let export = MediaEditorVideoExport(subject: exportSubject, configuration: configuration, outputPath: outputPath) let export = MediaEditorVideoExport(context: self.context, subject: exportSubject, configuration: configuration, outputPath: outputPath)
self.export = export self.export = export
export.startExport() export.startExport()
@ -1283,12 +1291,13 @@ public final class MediaEditorScreen: ViewController {
}) })
} else { } else {
if let image = mediaEditor.resultImage { if let image = mediaEditor.resultImage {
let resultImage = makeEditorImageComposition(inputImage: image, dimensions: storyDimensions, values: mediaEditor.values) makeEditorImageComposition(context: self.context, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { resultImage in
if let data = resultImage?.jpegData(compressionQuality: 0.8) { if let data = resultImage?.jpegData(compressionQuality: 0.8) {
let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).jpg" let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).jpg"
try? data.write(to: URL(fileURLWithPath: outputPath)) try? data.write(to: URL(fileURLWithPath: outputPath))
saveToPhotos(outputPath, false) saveToPhotos(outputPath, false)
} }
})
} }
} }
} }