Animated emoji skin coloring

This commit is contained in:
Ilya Laktyushin 2019-08-07 16:47:01 +03:00
parent d1bcfbc670
commit 40b1160bcd
19 changed files with 531 additions and 309 deletions

View File

@ -924,8 +924,11 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
}
updatedState.addMessages([message], location: .UpperHistoryBlock)
}
case let .updateServiceNotification(_, date, type, text, media, entities):
if let date = date {
case let .updateServiceNotification(flags, date, type, text, media, entities):
let popup = (flags & (1 << 0)) != 0
if popup {
updatedState.addDisplayAlert(text, isDropAuth: type.hasPrefix("AUTH_KEY_DROP_"))
} else if let date = date {
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000)
if updatedState.peers[peerId] == nil {
@ -969,8 +972,6 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
let message = StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: nil, groupingKey: nil, timestamp: date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: peerId, text: messageText, attributes: attributes, media: medias)
updatedState.addMessages([message], location: .UpperHistoryBlock)
}
} else {
updatedState.addDisplayAlert(text, isDropAuth: type.hasPrefix("AUTH_KEY_DROP_"))
}
case let .updateReadChannelInbox(_, folderId, channelId, maxId, stillUnreadCount, pts):
updatedState.resetIncomingReadState(groupId: PeerGroupId(rawValue: folderId ?? 0), peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), namespace: Namespaces.Message.Cloud, maxIncomingReadId: maxId, count: stillUnreadCount, pts: pts)

View File

@ -13,6 +13,13 @@ public struct HistoryPreloadIndex: Comparable {
public let isMuted: Bool
public let isPriority: Bool
public init(index: ChatListIndex?, hasUnread: Bool, isMuted: Bool, isPriority: Bool) {
self.index = index
self.hasUnread = hasUnread
self.isMuted = isMuted
self.isPriority = isPriority
}
public static func <(lhs: HistoryPreloadIndex, rhs: HistoryPreloadIndex) -> Bool {
if lhs.isPriority != rhs.isPriority {
if lhs.isPriority {

View File

@ -402,7 +402,7 @@ final class AnimatedStickerNode: ASDisplayNode {
self.addSubnode(self.renderer!)
}
func setup(account: Account, resource: MediaResource, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
func setup(account: Account, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, playbackMode: AnimatedStickerPlaybackMode = .loop, mode: AnimatedStickerMode) {
if width < 2 || height < 2 {
return
}
@ -410,27 +410,27 @@ final class AnimatedStickerNode: ASDisplayNode {
switch mode {
case .direct:
self.disposable.set((account.postbox.mediaBox.resourceData(resource)
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let strongSelf = self, data.complete else {
return
}
if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) {
strongSelf.directData = Tuple(directData, data.path, width, height)
}
|> deliverOnMainQueue).start(next: { [weak self] data in
guard let strongSelf = self, data.complete else {
return
}
if let directData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead]) {
strongSelf.directData = Tuple(directData, data.path, width, height)
}
if strongSelf.isPlaying {
strongSelf.play()
}
}))
case .cached:
self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, fitzModifier: fitzModifier, width: width, height: height, synchronousLoad: false)
|> deliverOnMainQueue).start(next: { [weak self] data in
if let strongSelf = self, data.complete {
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead])
if strongSelf.isPlaying {
strongSelf.play()
}
}))
case .cached:
self.disposable.set((chatMessageAnimationData(postbox: account.postbox, resource: resource, width: width, height: height, synchronousLoad: false)
|> deliverOnMainQueue).start(next: { [weak self] data in
if let strongSelf = self, data.complete {
strongSelf.cachedData = try? Data(contentsOf: URL(fileURLWithPath: data.path), options: [.mappedRead])
if strongSelf.isPlaying {
strongSelf.play()
}
}
}))
}
}))
}
}

View File

@ -65,7 +65,85 @@ public class LocalBundleResource: TelegramMediaResource {
}
}
func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: String) -> Signal<TempBoxFile, NoError> {
let colorKeyRegex = try? NSRegularExpression(pattern: "\"k\":\\[[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\,[\\d\\.]+\\]")
private func transformedWithFitzModifier(data: Data, fitzModifier: EmojiFitzModifier?) -> Data {
if let fitzModifier = fitzModifier, var string = String(data: data, encoding: .utf8) {
let color1: UIColor
let color2: UIColor
let color3: UIColor
let color4: UIColor
var colors: [UIColor] = [0xf77e41, 0xffb139, 0xffd140, 0xffdf79].map { UIColor(rgb: $0) }
let replacementColors: [UIColor]
switch fitzModifier {
case .type12:
replacementColors = [0xca907a, 0xedc5a5, 0xf7e3c3, 0xfbefd6].map { UIColor(rgb: $0) }
case .type3:
replacementColors = [0xaa7c60, 0xc8a987, 0xddc89f, 0xe6d6b2].map { UIColor(rgb: $0) }
case .type4:
replacementColors = [0x8c6148, 0xad8562, 0xc49e76, 0xd4b188].map { UIColor(rgb: $0) }
case .type5:
replacementColors = [0x6e3c2c, 0x925a34, 0xa16e46, 0xac7a52].map { UIColor(rgb: $0) }
case .type6:
replacementColors = [0x291c12, 0x472a22, 0x573b30, 0x68493c].map { UIColor(rgb: $0) }
}
func colorToString(_ color: UIColor) -> String {
var r: CGFloat = 0.0
var g: CGFloat = 0.0
var b: CGFloat = 0.0
if color.getRed(&r, green: &g, blue: &b, alpha: nil) {
return "\"k\":[\(r),\(g),\(b),1]"
}
return ""
}
func match(_ a: Double, _ b: Double, eps: Double) -> Bool {
return abs(a - b) < eps
}
var replacements: [(NSTextCheckingResult, String)] = []
if let colorKeyRegex = colorKeyRegex {
let results = colorKeyRegex.matches(in: string, range: NSRange(string.startIndex..., in: string))
for result in results.reversed() {
if let range = Range(result.range, in: string) {
let substring = String(string[range])
let color = substring[substring.index(string.startIndex, offsetBy: "\"k\":[".count) ..< substring.index(before: substring.endIndex)]
let components = color.split(separator: ",")
if components.count == 4, let r = Double(components[0]), let g = Double(components[1]), let b = Double(components[2]), let a = Double(components[3]) {
if match(a, 1.0, eps: 0.01) {
for i in 0 ..< colors.count {
let color = colors[i]
var cr: CGFloat = 0.0
var cg: CGFloat = 0.0
var cb: CGFloat = 0.0
if color.getRed(&cr, green: &cg, blue: &cb, alpha: nil) {
if match(r, Double(cr), eps: 0.01) && match(g, Double(cg), eps: 0.01) && match(b, Double(cb), eps: 0.01) {
replacements.append((result, colorToString(replacementColors[i])))
}
}
}
}
}
}
}
}
for (result, text) in replacements {
if let range = Range(result.range, in: string) {
string = string.replacingCharacters(in: range, with: text)
}
}
return string.data(using: .utf8) ?? data
} else {
return data
}
}
func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<TempBoxFile, NoError> {
return Signal({ subscriber in
let queue = Queue()
@ -77,72 +155,75 @@ func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, cacheKey: St
}
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
if cancelled.with({ $0 }) {
return
}
let context = DrawingContext(size: size, scale: 1.0, clear: true)
player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow))
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
assert(yuvaPixelsPerAlphaRow % 2 == 0)
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
var yuvaFrameData = malloc(yuvaLength)!
memset(yuvaFrameData, 0, yuvaLength)
defer {
free(yuvaFrameData)
}
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) {
let colorContext = DrawingContext(size: size, scale: 1.0, clear: false)
colorContext.withFlippedContext { c in
c.setFillColor(UIColor.black.cgColor)
c.fill(CGRect(origin: CGPoint(), size: size))
c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
}
guard let colorImage = colorContext.generateImage() else {
if let decompressedData = decompressedData {
let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier)
if let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
if cancelled.with({ $0 }) {
return
}
let colorData = NSMutableData()
let alphaData = NSMutableData()
let context = DrawingContext(size: size, scale: 1.0, clear: true)
player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow))
if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
assert(yuvaPixelsPerAlphaRow % 2 == 0)
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
var yuvaFrameData = malloc(yuvaLength)!
memset(yuvaFrameData, 0, yuvaLength)
defer {
free(yuvaFrameData)
}
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow))
if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) {
let colorContext = DrawingContext(size: size, scale: 1.0, clear: false)
colorContext.withFlippedContext { c in
c.setFillColor(UIColor.black.cgColor)
c.fill(CGRect(origin: CGPoint(), size: size))
c.draw(colorSourceImage.cgImage!, in: CGRect(origin: CGPoint(), size: size))
}
guard let colorImage = colorContext.generateImage() else {
return
}
let colorQuality: Float
let alphaQuality: Float
colorQuality = 0.5
alphaQuality = 0.4
let colorData = NSMutableData()
let alphaData = NSMutableData()
let options = NSMutableDictionary()
options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
let optionsAlpha = NSMutableDictionary()
optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary)
CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, optionsAlpha as CFDictionary)
if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) {
let finalData = NSMutableData()
var colorSize: Int32 = Int32(colorData.length)
finalData.append(&colorSize, length: 4)
finalData.append(colorData as Data)
var alphaSize: Int32 = Int32(alphaData.length)
finalData.append(&alphaSize, length: 4)
finalData.append(alphaData as Data)
if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg")
let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: [])
subscriber.putNext(tempFile)
subscriber.putCompletion()
let colorQuality: Float
let alphaQuality: Float
colorQuality = 0.5
alphaQuality = 0.4
let options = NSMutableDictionary()
options.setObject(colorQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
let optionsAlpha = NSMutableDictionary()
optionsAlpha.setObject(alphaQuality as NSNumber, forKey: kCGImageDestinationLossyCompressionQuality as NSString)
CGImageDestinationAddImage(colorDestination, colorImage.cgImage!, options as CFDictionary)
CGImageDestinationAddImage(alphaDestination, alphaImage.cgImage!, optionsAlpha as CFDictionary)
if CGImageDestinationFinalize(colorDestination) && CGImageDestinationFinalize(alphaDestination) {
let finalData = NSMutableData()
var colorSize: Int32 = Int32(colorData.length)
finalData.append(&colorSize, length: 4)
finalData.append(colorData as Data)
var alphaSize: Int32 = Int32(alphaData.length)
finalData.append(&alphaSize, length: 4)
finalData.append(alphaData as Data)
let tempFile = TempBox.shared.tempFile(fileName: "image.ajpg")
let _ = try? finalData.write(to: URL(fileURLWithPath: tempFile.path), options: [])
subscriber.putNext(tempFile)
subscriber.putCompletion()
}
}
}
}
@ -159,7 +240,7 @@ private let threadPool: ThreadPool = {
}()
@available(iOS 9.0, *)
func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, cacheKey: String) -> Signal<String, NoError> {
func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, cacheKey: String) -> Signal<String, NoError> {
return Signal({ subscriber in
let cancelled = Atomic<Bool>(value: false)
@ -174,127 +255,130 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize,
var appendingTime: Double = 0
var deltaTime: Double = 0
var compressionTime: Double = 0
let decompressedData = TGGUnzipData(data, 8 * 1024 * 1024)
if let decompressedData = decompressedData, let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
let endFrame = Int(player.frameCount)
if cancelled.with({ $0 }) {
//print("cancelled 2")
return
}
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let path = NSTemporaryDirectory() + "\(randomId).lz4v"
guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
return
}
let bytesPerRow = (4 * Int(size.width) + 15) & (~15)
var currentFrame: Int32 = 0
var fps: Int32 = player.frameRate
var frameCount: Int32 = player.frameCount
let _ = fileContext.write(&fps, count: 4)
let _ = fileContext.write(&frameCount, count: 4)
var widthValue: Int32 = Int32(size.width)
var heightValue: Int32 = Int32(size.height)
var bytesPerRowValue: Int32 = Int32(bytesPerRow)
let _ = fileContext.write(&widthValue, count: 4)
let _ = fileContext.write(&heightValue, count: 4)
let _ = fileContext.write(&bytesPerRowValue, count: 4)
let frameLength = bytesPerRow * Int(size.height)
assert(frameLength % 16 == 0)
let currentFrameData = malloc(frameLength)!
memset(currentFrameData, 0, frameLength)
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
assert(yuvaPixelsPerAlphaRow % 2 == 0)
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
var yuvaFrameData = malloc(yuvaLength)!
memset(yuvaFrameData, 0, yuvaLength)
var previousYuvaFrameData = malloc(yuvaLength)!
memset(previousYuvaFrameData, 0, yuvaLength)
defer {
free(currentFrameData)
free(previousYuvaFrameData)
free(yuvaFrameData)
}
var compressedFrameData = Data(count: frameLength)
let compressedFrameDataLength = compressedFrameData.count
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))!
defer {
free(scratchData)
}
while currentFrame < endFrame {
if let decompressedData = decompressedData {
let decompressedData = transformedWithFitzModifier(data: decompressedData, fitzModifier: fitzModifier)
if let player = LottieInstance(data: decompressedData, cacheKey: cacheKey) {
let endFrame = Int(player.frameCount)
if cancelled.with({ $0 }) {
//print("cancelled 3")
//print("cancelled 2")
return
}
let drawStartTime = CACurrentMediaTime()
var randomId: Int64 = 0
arc4random_buf(&randomId, 8)
let path = NSTemporaryDirectory() + "\(randomId).lz4v"
guard let fileContext = ManagedFile(queue: nil, path: path, mode: .readwrite) else {
return
}
let bytesPerRow = (4 * Int(size.width) + 15) & (~15)
var currentFrame: Int32 = 0
var fps: Int32 = player.frameRate
var frameCount: Int32 = player.frameCount
let _ = fileContext.write(&fps, count: 4)
let _ = fileContext.write(&frameCount, count: 4)
var widthValue: Int32 = Int32(size.width)
var heightValue: Int32 = Int32(size.height)
var bytesPerRowValue: Int32 = Int32(bytesPerRow)
let _ = fileContext.write(&widthValue, count: 4)
let _ = fileContext.write(&heightValue, count: 4)
let _ = fileContext.write(&bytesPerRowValue, count: 4)
let frameLength = bytesPerRow * Int(size.height)
assert(frameLength % 16 == 0)
let currentFrameData = malloc(frameLength)!
memset(currentFrameData, 0, frameLength)
player.renderFrame(with: Int32(currentFrame), into: currentFrameData.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(bytesPerRow))
drawingTime += CACurrentMediaTime() - drawStartTime
let appendStartTime = CACurrentMediaTime()
let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1)
assert(yuvaPixelsPerAlphaRow % 2 == 0)
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), currentFrameData.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(bytesPerRow))
let yuvaLength = Int(size.width) * Int(size.height) * 2 + yuvaPixelsPerAlphaRow * Int(size.height) / 2
var yuvaFrameData = malloc(yuvaLength)!
memset(yuvaFrameData, 0, yuvaLength)
appendingTime += CACurrentMediaTime() - appendStartTime
var previousYuvaFrameData = malloc(yuvaLength)!
memset(previousYuvaFrameData, 0, yuvaLength)
let deltaStartTime = CACurrentMediaTime()
var lhs = previousYuvaFrameData.assumingMemoryBound(to: UInt64.self)
var rhs = yuvaFrameData.assumingMemoryBound(to: UInt64.self)
for _ in 0 ..< yuvaLength / 8 {
lhs.pointee = rhs.pointee ^ lhs.pointee
lhs = lhs.advanced(by: 1)
rhs = rhs.advanced(by: 1)
}
var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
for _ in (yuvaLength / 8) * 8 ..< yuvaLength {
lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee
lhsRest = lhsRest.advanced(by: 1)
rhsRest = rhsRest.advanced(by: 1)
}
deltaTime += CACurrentMediaTime() - deltaStartTime
let compressionStartTime = CACurrentMediaTime()
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
var frameLengthValue: Int32 = Int32(length)
let _ = fileContext.write(&frameLengthValue, count: 4)
let _ = fileContext.write(bytes, count: length)
defer {
free(currentFrameData)
free(previousYuvaFrameData)
free(yuvaFrameData)
}
let tmp = previousYuvaFrameData
previousYuvaFrameData = yuvaFrameData
yuvaFrameData = tmp
var compressedFrameData = Data(count: frameLength)
let compressedFrameDataLength = compressedFrameData.count
compressionTime += CACurrentMediaTime() - compressionStartTime
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZFSE))!
defer {
free(scratchData)
}
currentFrame += 1
while currentFrame < endFrame {
if cancelled.with({ $0 }) {
//print("cancelled 3")
return
}
let drawStartTime = CACurrentMediaTime()
memset(currentFrameData, 0, frameLength)
player.renderFrame(with: Int32(currentFrame), into: currentFrameData.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(bytesPerRow))
drawingTime += CACurrentMediaTime() - drawStartTime
let appendStartTime = CACurrentMediaTime()
encodeRGBAToYUVA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), currentFrameData.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(bytesPerRow))
appendingTime += CACurrentMediaTime() - appendStartTime
let deltaStartTime = CACurrentMediaTime()
var lhs = previousYuvaFrameData.assumingMemoryBound(to: UInt64.self)
var rhs = yuvaFrameData.assumingMemoryBound(to: UInt64.self)
for _ in 0 ..< yuvaLength / 8 {
lhs.pointee = rhs.pointee ^ lhs.pointee
lhs = lhs.advanced(by: 1)
rhs = rhs.advanced(by: 1)
}
var lhsRest = previousYuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
var rhsRest = yuvaFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: (yuvaLength / 8) * 8)
for _ in (yuvaLength / 8) * 8 ..< yuvaLength {
lhsRest.pointee = rhsRest.pointee ^ lhsRest.pointee
lhsRest = lhsRest.advanced(by: 1)
rhsRest = rhsRest.advanced(by: 1)
}
deltaTime += CACurrentMediaTime() - deltaStartTime
let compressionStartTime = CACurrentMediaTime()
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousYuvaFrameData.assumingMemoryBound(to: UInt8.self), yuvaLength, scratchData, COMPRESSION_LZFSE)
var frameLengthValue: Int32 = Int32(length)
let _ = fileContext.write(&frameLengthValue, count: 4)
let _ = fileContext.write(bytes, count: length)
}
let tmp = previousYuvaFrameData
previousYuvaFrameData = yuvaFrameData
yuvaFrameData = tmp
compressionTime += CACurrentMediaTime() - compressionStartTime
currentFrame += 1
}
subscriber.putNext(path)
subscriber.putCompletion()
print("animation render time \(CACurrentMediaTime() - startTime)")
print("of which drawing time \(drawingTime)")
print("of which appending time \(appendingTime)")
print("of which delta time \(deltaTime)")
print("of which compression time \(compressionTime)")
}
subscriber.putNext(path)
subscriber.putCompletion()
print("animation render time \(CACurrentMediaTime() - startTime)")
print("of which drawing time \(drawingTime)")
print("of which appending time \(appendingTime)")
print("of which delta time \(deltaTime)")
print("of which compression time \(compressionTime)")
}
}))
return ActionDisposable {

View File

@ -234,19 +234,51 @@ final class CachedEmojiRepresentation: CachedMediaResourceRepresentation {
}
}
public enum EmojiFitzModifier: Int32, Equatable {
case type12
case type3
case type4
case type5
case type6
public init?(emoji: String) {
switch emoji.unicodeScalars.first?.value {
case 0x1f3fb:
self = .type12
case 0x1f3fc:
self = .type3
case 0x1f3fd:
self = .type4
case 0x1f3fe:
self = .type5
case 0x1f3ff:
self = .type6
default:
return nil
}
}
}
final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRepresentation {
let keepDuration: CachedMediaRepresentationKeepDuration = .general
let width: Int32
let height: Int32
let fitzModifier: EmojiFitzModifier?
init(width: Int32, height: Int32) {
init(width: Int32, height: Int32, fitzModifier: EmojiFitzModifier? = nil) {
self.width = width
self.height = height
self.fitzModifier = fitzModifier
}
var uniqueId: String {
return "animated-sticker-first-frame-\(self.width)x\(self.height)-v1"
let version: Int = 1
if let fitzModifier = self.fitzModifier {
return "animated-sticker-first-frame-\(self.width)x\(self.height)-fitz\(fitzModifier.rawValue)-v\(version)"
} else {
return "animated-sticker-first-frame-\(self.width)x\(self.height)-v\(version)"
}
}
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
@ -257,6 +289,9 @@ final class CachedAnimatedStickerFirstFrameRepresentation: CachedMediaResourceRe
if other.height != self.height {
return false
}
if other.fitzModifier != self.fitzModifier {
return false
}
return true
} else {
return false
@ -269,14 +304,21 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati
let width: Int32
let height: Int32
let fitzModifier: EmojiFitzModifier?
var uniqueId: String {
return "animated-sticker-\(self.width)x\(self.height)-v8"
let version: Int = 8
if let fitzModifier = self.fitzModifier {
return "animated-sticker-\(self.width)x\(self.height)-fitz\(fitzModifier.rawValue)-v\(version)"
} else {
return "animated-sticker-\(self.width)x\(self.height)-v\(version)"
}
}
init(width: Int32, height: Int32) {
init(width: Int32, height: Int32, fitzModifier: EmojiFitzModifier? = nil) {
self.width = width
self.height = height
self.fitzModifier = fitzModifier
}
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
@ -287,6 +329,9 @@ final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentati
if other.height != self.height {
return false
}
if other.fitzModifier != self.fitzModifier {
return false
}
return true
} else {
return false

View File

@ -133,7 +133,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode {
func setFile(context: AccountContext, fileReference: FileMediaReference) {
if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) {
let signal = chatMessageAnimatedStrickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
let signal = chatMessageAnimatedStickerBackingData(postbox: context.account.postbox, fileReference: fileReference, synchronousLoad: false)
|> mapToSignal { value -> Signal<Data, NoError> in
if value._1, let data = value._0 {
return .single(data)

View File

@ -39,7 +39,7 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
var contentTypeHint: ChatMessageEntryContentType = .generic
if presentationData.largeEmoji {
if let _ = associatedData.animatedEmojiStickers[entry.message.text.trimmedEmoji] {
if let _ = associatedData.animatedEmojiStickers[entry.message.text.basicEmoji.0] {
contentTypeHint = .animatedEmoji
} else if messageIsElligibleForLargeEmoji(entry.message) {
contentTypeHint = .largeEmoji

View File

@ -545,7 +545,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
var animatedEmojiStickers: [String: StickerPackItem] = [:]
for case let item as StickerPackItem in items {
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
animatedEmojiStickers[emoji.trimmedEmoji] = item
animatedEmojiStickers[emoji.basicEmoji.0] = item
}
}
return animatedEmojiStickers

View File

@ -176,7 +176,7 @@ func inputContextQueriesForChatPresentationIntefaceState(_ chatPresentationInter
for (possibleQueryRange, possibleTypes, additionalStringRange) in textInputStateContextQueryRangeAndType(inputState) {
let query = inputString.substring(with: possibleQueryRange)
if possibleTypes == [.emoji] {
result.append(.emoji(query.basicEmoji))
result.append(.emoji(query.basicEmoji.0))
} else if possibleTypes == [.hashtag] {
result.append(.hashtag(query))
} else if possibleTypes == [.mention] {

View File

@ -88,7 +88,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
case .installed:
scope = [.installed]
}
return searchStickers(account: context.account, query: query.trimmedEmoji, scope: scope)
return searchStickers(account: context.account, query: query.basicEmoji.0, scope: scope)
|> introduceError(ChatContextQueryError.self)
}
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in

View File

@ -231,12 +231,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[item.message.text.trimmedEmoji]?.file {
let (emoji, fitz) = item.message.text.basicEmoji
if self.telegramFile == nil, let emojiFile = item.associatedData.animatedEmojiStickers[emoji]?.file {
if self.emojiFile?.id != emojiFile.id {
self.emojiFile = emojiFile
let dimensions = emojiFile.dimensions ?? CGSize(width: 512.0, height: 512.0)
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), thumbnail: false))
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .message(message: MessageReference(item.message), media: emojiFile)).start())
var fitzModifier: EmojiFitzModifier?
if let fitz = fitz {
fitzModifier = EmojiFitzModifier(emoji: fitz)
}
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.context.account.postbox, file: emojiFile, small: false, size: dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)), fitzModifier: fitzModifier, thumbnail: false))
self.disposable.set(freeMediaFileInteractiveFetched(account: item.context.account, fileReference: .standalone(media: emojiFile)).start())
self.updateVisibility()
}
}
@ -269,7 +274,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var file: TelegramMediaFile?
var playbackMode: AnimatedStickerPlaybackMode = .loop
var isEmoji = false
var fitzModifier: EmojiFitzModifier?
if let telegramFile = self.telegramFile {
file = telegramFile
if !item.controllerInteraction.stickerSettings.loopAnimatedStickers {
@ -279,12 +285,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
isEmoji = true
file = emojiFile
playbackMode = .once
let (_, fitz) = item.message.text.basicEmoji
if let fitz = fitz {
fitzModifier = EmojiFitzModifier(emoji: fitz)
}
}
if let file = file {
let dimensions = file.dimensions ?? CGSize(width: 512.0, height: 512.0)
let fittedSize = isEmoji ? dimensions.aspectFilled(CGSize(width: 384.0, height: 384.0)) : dimensions.aspectFitted(CGSize(width: 384.0, height: 384.0))
self.animationNode.setup(account: item.context.account, resource: file.resource, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached)
self.animationNode.setup(account: item.context.account, resource: file.resource, fitzModifier: fitzModifier, width: Int(fittedSize.width), height: Int(fittedSize.height), playbackMode: playbackMode, mode: .cached)
}
}
}

View File

@ -387,7 +387,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
}
if viewClassName == ChatMessageBubbleItemNode.self && self.presentationData.largeEmoji && messageIsElligibleForLargeEmoji(self.message) {
if let _ = self.associatedData.animatedEmojiStickers[self.message.text.trimmedEmoji] {
if let _ = self.associatedData.animatedEmojiStickers[self.message.text.basicEmoji.0] {
viewClassName = ChatMessageAnimatedStickerItemNode.self
} else {
viewClassName = ChatMessageStickerItemNode.self

View File

@ -129,7 +129,7 @@ extension String {
return string
}
var basicEmoji: String {
var basicEmoji: (String, String?) {
let fitzCodes: [UInt32] = [
0x1f3fb,
0x1f3fc,
@ -139,13 +139,18 @@ extension String {
]
var string = ""
var fitzModifier: String?
for scalar in self.unicodeScalars {
if fitzCodes.contains(scalar.value) {
fitzModifier = String(scalar)
continue
}
string.unicodeScalars.append(scalar)
if scalar.value == 0x2764, self.unicodeScalars.count > 1, self.emojis.count == 1 {
break
}
}
return string
return (string, fitzModifier)
}
var trimmedEmoji: String {

View File

@ -112,19 +112,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
} else if let representation = representation as? CachedEmojiRepresentation {
return fetchEmojiRepresentation(account: account, resource: resource, representation: representation)
} else if let representation = representation as? CachedAnimatedStickerRepresentation {
let data: Signal<MediaResourceData, NoError>
// if let resource = resource as? LocalBundleResource {
// data = Signal { subscriber in
// if let path = frameworkBundle.path(forResource: resource.name, ofType: resource.ext), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedRead]) {
// subscriber.putNext(MediaResourceData(path: path, offset: 0, size: data.count, complete: true))
// subscriber.putCompletion()
// }
// return EmptyDisposable
// }
// } else {
data = account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
// }
return data
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
if !data.complete {
return .complete()
@ -908,7 +896,7 @@ private func fetchEmojiRepresentation(account: Account, resource: MediaResource,
private func fetchAnimatedStickerFirstFrameRepresentation(account: Account, resource: MediaResource, resourceData: MediaResourceData, representation: CachedAnimatedStickerFirstFrameRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
return Signal({ subscriber in
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { file in
return fetchCompressedLottieFirstFrameAJpeg(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { file in
subscriber.putNext(.tempFile(file))
subscriber.putCompletion()
})
@ -923,7 +911,7 @@ private func fetchAnimatedStickerRepresentation(account: Account, resource: Medi
return Signal({ subscriber in
if let data = try? Data(contentsOf: URL(fileURLWithPath: resourceData.path), options: [.mappedIfSafe]) {
if #available(iOS 9.0, *) {
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: CGFloat(representation.width), height: CGFloat(representation.height)), fitzModifier: representation.fitzModifier, cacheKey: "\(resource.id.uniqueId)-\(representation.uniqueId)").start(next: { path in
subscriber.putNext(.temporaryPath(path))
subscriber.putCompletion()
})

View File

@ -9,6 +9,12 @@ public func freeMediaFileInteractiveFetched(account: Account, fileReference: Fil
return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(fileReference.media.resource))
}
func freeMediaFileInteractiveFetched(fetchManager: FetchManager, fileReference: FileMediaReference, priority: FetchManagerPriority) -> Signal<Void, NoError> {
let file = fileReference.media
let mediaReference = AnyMediaReference.standalone(media: fileReference.media)
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(PeerId(namespace: 0, id: 0)), locationKey: .free, mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: IndexSet(integersIn: 0 ..< Int(Int32.max) as Range<Int>), statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: false, priority: priority)
}
func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal<FetchResourceSourceType, FetchResourceError> {
return fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(resource))
}

View File

@ -11,6 +11,11 @@ private final class PrefetchMediaContext {
}
}
public enum PrefetchMediaItem {
case chatHistory(ChatHistoryPreloadMediaItem)
case animatedEmojiSticker(TelegramMediaFile)
}
private final class PrefetchManagerImpl {
private let queue: Queue
private let account: Account
@ -36,7 +41,37 @@ private final class PrefetchManagerImpl {
}
|> distinctUntilChanged
self.listDisposable = (combineLatest(account.viewTracker.orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType)
let orderedPreloadMedia = account.viewTracker.orderedPreloadMedia
|> mapToSignal { orderedPreloadMedia in
return loadedStickerPack(postbox: account.postbox, network: account.network, reference: .animatedEmoji, forceActualized: false)
|> map { result -> [PrefetchMediaItem] in
let chatHistoryMediaItems = orderedPreloadMedia.map { PrefetchMediaItem.chatHistory($0) }
switch result {
case let .result(_, items, _):
var animatedEmojiStickers: [String: StickerPackItem] = [:]
for case let item as StickerPackItem in items {
if let emoji = item.getStringRepresentationsOfIndexKeys().first {
animatedEmojiStickers[emoji.basicEmoji.0] = item
}
}
var stickerItems: [PrefetchMediaItem] = []
let popularEmoji = ["\u{2764}", "👍", "😳", "😒", "🥳"]
for emoji in popularEmoji {
if let sticker = animatedEmojiStickers[emoji] {
if let _ = account.postbox.mediaBox.completedResourcePath(sticker.file.resource) {
} else {
stickerItems.append(.animatedEmojiSticker(sticker.file))
}
}
}
return stickerItems + chatHistoryMediaItems
default:
return chatHistoryMediaItems
}
}
}
self.listDisposable = (combineLatest(orderedPreloadMedia, sharedContext.automaticMediaDownloadSettings, networkType)
|> deliverOn(self.queue)).start(next: { [weak self] orderedPreloadMedia, automaticDownloadSettings, networkType in
self?.updateOrderedPreloadMedia(orderedPreloadMedia, automaticDownloadSettings: automaticDownloadSettings, networkType: networkType)
})
@ -47,79 +82,119 @@ private final class PrefetchManagerImpl {
self.listDisposable?.dispose()
}
private func updateOrderedPreloadMedia(_ orderedPreloadMedia: [ChatHistoryPreloadMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) {
private func updateOrderedPreloadMedia(_ items: [PrefetchMediaItem], automaticDownloadSettings: MediaAutoDownloadSettings, networkType: MediaAutoDownloadNetworkType) {
var validIds = Set<MediaId>()
for mediaItem in orderedPreloadMedia {
guard let id = mediaItem.media.media.id else {
continue
}
if validIds.contains(id) {
continue
}
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
let peerType: MediaAutoDownloadPeerType
if mediaItem.media.authorIsContact {
peerType = .contact
} else if let channel = mediaItem.media.peer as? TelegramChannel {
if case .group = channel.info {
peerType = .group
} else {
peerType = .channel
}
} else if mediaItem.media.peer is TelegramGroup {
peerType = .group
} else {
peerType = .otherPrivate
}
var mediaResource: MediaResource?
if let telegramImage = mediaItem.media.media as? TelegramMediaImage {
mediaResource = largestRepresentationForPhoto(telegramImage)?.resource
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) {
automaticDownload = .full
}
} else if let telegramFile = mediaItem.media.media as? TelegramMediaFile {
mediaResource = telegramFile.resource
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) {
automaticDownload = .full
} else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) {
automaticDownload = .prefetch
}
}
if case .none = automaticDownload {
continue
}
guard let resource = mediaResource else {
continue
}
validIds.insert(id)
let context: PrefetchMediaContext
if let current = self.contexts[id] {
context = current
} else {
context = PrefetchMediaContext()
self.contexts[id] = context
let media = mediaItem.media.media
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: mediaItem.preloadIndex, localOrder: mediaItem.media.index)
if case .full = automaticDownload {
if let image = media as? TelegramMediaImage {
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
} else if let _ = media as? TelegramMediaWebFile {
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
} else if let file = media as? TelegramMediaFile {
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
context.fetchDisposable.set(fetchSignal.start())
var order: Int32 = 0
for mediaItem in items {
switch mediaItem {
case let .chatHistory(mediaItem):
guard let id = mediaItem.media.media.id else {
continue
}
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
if let file = media as? TelegramMediaFile, let _ = file.size {
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
if validIds.contains(id) {
continue
}
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
let peerType: MediaAutoDownloadPeerType
if mediaItem.media.authorIsContact {
peerType = .contact
} else if let channel = mediaItem.media.peer as? TelegramChannel {
if case .group = channel.info {
peerType = .group
} else {
peerType = .channel
}
} else if mediaItem.media.peer is TelegramGroup {
peerType = .group
} else {
peerType = .otherPrivate
}
var mediaResource: MediaResource?
if let telegramImage = mediaItem.media.media as? TelegramMediaImage {
mediaResource = largestRepresentationForPhoto(telegramImage)?.resource
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramImage) {
automaticDownload = .full
}
} else if let telegramFile = mediaItem.media.media as? TelegramMediaFile {
mediaResource = telegramFile.resource
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: telegramFile) {
automaticDownload = .full
} else if shouldPredownloadMedia(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, media: telegramFile) {
automaticDownload = .prefetch
}
}
if case .none = automaticDownload {
continue
}
guard let resource = mediaResource else {
continue
}
validIds.insert(id)
let context: PrefetchMediaContext
if let current = self.contexts[id] {
context = current
} else {
context = PrefetchMediaContext()
self.contexts[id] = context
let media = mediaItem.media.media
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: mediaItem.preloadIndex, localOrder: mediaItem.media.index)
if case .full = automaticDownload {
if let image = media as? TelegramMediaImage {
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
} else if let _ = media as? TelegramMediaWebFile {
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
} else if let file = media as? TelegramMediaFile {
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
context.fetchDisposable.set(fetchSignal.start())
}
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
if let file = media as? TelegramMediaFile, let _ = file.size {
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
}
}
}
case let .animatedEmojiSticker(media):
guard let id = media.id else {
continue
}
if validIds.contains(id) {
continue
}
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
let peerType = MediaAutoDownloadPeerType.contact
if shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: peerType, networkType: networkType, authorPeerId: nil, contactsPeerIds: [], media: media) {
automaticDownload = .full
}
if case .none = automaticDownload {
continue
}
validIds.insert(id)
let context: PrefetchMediaContext
if let current = self.contexts[id] {
context = current
} else {
context = PrefetchMediaContext()
self.contexts[id] = context
let priority: FetchManagerPriority = .backgroundPrefetch(locationOrder: HistoryPreloadIndex(index: nil, hasUnread: false, isMuted: false, isPriority: true), localOrder: MessageIndex(id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: 0, id: order), timestamp: 0))
if case .full = automaticDownload {
let fetchSignal = freeMediaFileInteractiveFetched(fetchManager: self.fetchManager, fileReference: .standalone(media: media), priority: priority)
context.fetchDisposable.set(fetchSignal.start())
}
order += 1
}
}
}

View File

@ -134,6 +134,9 @@ public final class PrincipalThemeEssentialGraphics {
let emptyImage = UIImage()
if preview {
self.chatMessageBackgroundIncomingImage = messageBubbleImage(incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none)
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none)
self.checkBubbleFullImage = generateCheckImage(partial: false, color: theme.message.outgoingCheckColor)!
self.checkBubblePartialImage = generateCheckImage(partial: true, color: theme.message.outgoingCheckColor)!
self.chatMessageBackgroundIncomingHighlightedImage = emptyImage
self.chatMessageBackgroundIncomingMergedTopImage = emptyImage
self.chatMessageBackgroundIncomingMergedTopHighlightedImage = emptyImage
@ -145,7 +148,6 @@ public final class PrincipalThemeEssentialGraphics {
self.chatMessageBackgroundIncomingMergedBothHighlightedImage = emptyImage
self.chatMessageBackgroundIncomingMergedSideImage = emptyImage
self.chatMessageBackgroundIncomingMergedSideHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingImage = messageBubbleImage(incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none)
self.chatMessageBackgroundOutgoingHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingMergedTopImage = emptyImage
self.chatMessageBackgroundOutgoingMergedTopHighlightedImage = emptyImage
@ -157,8 +159,6 @@ public final class PrincipalThemeEssentialGraphics {
self.chatMessageBackgroundOutgoingMergedBothHighlightedImage = emptyImage
self.chatMessageBackgroundOutgoingMergedSideImage = emptyImage
self.chatMessageBackgroundOutgoingMergedSideHighlightedImage = emptyImage
self.checkBubbleFullImage = emptyImage
self.checkBubblePartialImage = emptyImage
self.checkMediaFullImage = emptyImage
self.checkMediaPartialImage = emptyImage
self.checkFreeFullImage = emptyImage

View File

@ -269,8 +269,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isSingleEmoji {
signals = .single([searchStickers(account: account, query: text.trimmedEmoji)
//|> take(1)
signals = .single([searchStickers(account: account, query: text.basicEmoji.0)
|> map { (nil, $0) }])
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
var signal = searchEmojiKeywords(postbox: account.postbox, inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
@ -292,7 +291,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = []
let emoticons = keywords.flatMap { $0.emoticons }
for emoji in emoticons {
signals.append(searchStickers(account: self.context.account, query: emoji.trimmedEmoji)
signals.append(searchStickers(account: self.context.account, query: emoji.basicEmoji.0)
|> take(1)
|> map { (emoji, $0) })
}

View File

@ -96,11 +96,12 @@ private func chatMessageStickerDatas(postbox: Postbox, file: TelegramMediaFile,
}
}
func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
public func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, fetched: Bool, onlyFullSize: Bool, synchronousLoad: Bool) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
let thumbnailResource = chatMessageStickerResource(file: file, small: true)
let resource = chatMessageStickerResource(file: file, small: small)
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height)), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
let firstFrameRepresentation = CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height), fitzModifier: fitzModifier)
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: firstFrameRepresentation, complete: false, fetch: false, attemptSynchronously: synchronousLoad)
return maybeFetched
|> take(1)
@ -111,7 +112,7 @@ func chatMessageAnimatedStickerDatas(postbox: Postbox, file: TelegramMediaFile,
return .single(Tuple(nil, loadedData, true))
} else {
let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false)
let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerFirstFrameRepresentation(width: Int32(size.width), height: Int32(size.height)), complete: onlyFullSize)
let fullSizeData = postbox.mediaBox.cachedResourceRepresentation(resource, representation: firstFrameRepresentation, complete: onlyFullSize)
|> map { next in
return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete)
}
@ -162,7 +163,7 @@ private func chatMessageStickerThumbnailData(postbox: Postbox, file: TelegramMed
let thumbnailData = postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false)
return Signal { subscriber in
var fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start()
let fetchThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start()
let disposable = (thumbnailData
|> map { thumbnailData -> Data? in
@ -225,8 +226,9 @@ private func chatMessageStickerPackThumbnailData(postbox: Postbox, resource: Med
}
}
func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, width: Int, height: Int, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height)), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
public func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, fitzModifier: EmojiFitzModifier? = nil, width: Int, height: Int, synchronousLoad: Bool) -> Signal<MediaResourceData, NoError> {
let representation = CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height), fitzModifier: fitzModifier)
let maybeFetched = postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false, fetch: false, attemptSynchronously: synchronousLoad)
return maybeFetched
|> take(1)
@ -234,12 +236,12 @@ func chatMessageAnimationData(postbox: Postbox, resource: MediaResource, width:
if maybeData.complete {
return .single(maybeData)
} else {
return postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedAnimatedStickerRepresentation(width: Int32(width), height: Int32(height)), complete: false)
return postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: false)
}
}
}
func chatMessageAnimatedStrickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<Tuple2<Data?, Bool>, NoError> {
public func chatMessageAnimatedStickerBackingData(postbox: Postbox, fileReference: FileMediaReference, synchronousLoad: Bool) -> Signal<Tuple2<Data?, Bool>, NoError> {
let resource = fileReference.media.resource
let maybeFetched = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
@ -464,7 +466,7 @@ public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small:
}
}
public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile, small: Bool, size: CGSize, fitzModifier: EmojiFitzModifier? = nil, fetched: Bool = false, onlyFullSize: Bool = false, thumbnail: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal: Signal<Tuple3<Data?, Data?, Bool>, NoError>
if thumbnail {
signal = chatMessageStickerThumbnailData(postbox: postbox, file: file, synchronousLoad: synchronousLoad)
@ -472,7 +474,7 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile
return Tuple(data, nil, false)
}
} else {
signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad)
signal = chatMessageAnimatedStickerDatas(postbox: postbox, file: file, small: small, size: size, fitzModifier: fitzModifier, fetched: fetched, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad)
}
return signal
|> map { value in