mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-03 21:16:35 +00:00
Experimental animated stickers
This commit is contained in:
parent
05425b680a
commit
550be6b560
@ -4,12 +4,11 @@ import UIKit
|
||||
let deviceColorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let deviceScale = UIScreen.main.scale
|
||||
|
||||
public func generateImagePixel(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMutablePointer<Int8>) -> Void) -> UIImage? {
|
||||
let scale = deviceScale
|
||||
public func generateImagePixel(_ size: CGSize, scale: CGFloat, pixelGenerator: (CGSize, UnsafeMutablePointer<UInt8>) -> Void) -> UIImage? {
|
||||
let scaledSize = CGSize(width: size.width * scale, height: size.height * scale)
|
||||
let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
|
||||
let length = bytesPerRow * Int(scaledSize.height)
|
||||
let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self)
|
||||
let bytes = malloc(length)!.assumingMemoryBound(to: UInt8.self)
|
||||
guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in
|
||||
free(bytes)
|
||||
})
|
||||
@ -287,7 +286,7 @@ public class DrawingContext {
|
||||
}
|
||||
}
|
||||
|
||||
public init(size: CGSize, scale: CGFloat = 0.0, clear: Bool = false) {
|
||||
public init(size: CGSize, scale: CGFloat = 0.0, premultiplied: Bool = true, clear: Bool = false) {
|
||||
let actualScale: CGFloat
|
||||
if scale.isZero {
|
||||
actualScale = deviceScale
|
||||
@ -301,7 +300,11 @@ public class DrawingContext {
|
||||
self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
|
||||
self.length = bytesPerRow * Int(scaledSize.height)
|
||||
|
||||
self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
|
||||
if premultiplied {
|
||||
self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
|
||||
} else {
|
||||
self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.first.rawValue)
|
||||
}
|
||||
|
||||
self.bytes = malloc(length)!
|
||||
if clear {
|
||||
|
||||
@ -44,42 +44,8 @@ int iosMajorVersion()
|
||||
{
|
||||
static bool initialized = false;
|
||||
static int version = 7;
|
||||
if (!initialized)
|
||||
{
|
||||
switch ([[[UIDevice currentDevice] systemVersion] intValue])
|
||||
{
|
||||
case 4:
|
||||
version = 4;
|
||||
break;
|
||||
case 5:
|
||||
version = 5;
|
||||
break;
|
||||
case 6:
|
||||
version = 6;
|
||||
break;
|
||||
case 7:
|
||||
version = 7;
|
||||
break;
|
||||
case 8:
|
||||
version = 8;
|
||||
break;
|
||||
case 9:
|
||||
version = 9;
|
||||
break;
|
||||
case 10:
|
||||
version = 10;
|
||||
break;
|
||||
case 11:
|
||||
version = 11;
|
||||
break;
|
||||
case 12:
|
||||
version = 12;
|
||||
break;
|
||||
default:
|
||||
version = 9;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
version = [[[UIDevice currentDevice] systemVersion] intValue];
|
||||
initialized = true;
|
||||
}
|
||||
return version;
|
||||
@ -93,8 +59,9 @@ int iosMinorVersion()
|
||||
{
|
||||
NSString *versionString = [[UIDevice currentDevice] systemVersion];
|
||||
NSRange range = [versionString rangeOfString:@"."];
|
||||
if (range.location != NSNotFound)
|
||||
if (range.location != NSNotFound) {
|
||||
version = [[versionString substringFromIndex:range.location + 1] intValue];
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
@ -940,7 +940,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
||||
|
||||
#if DEBUG
|
||||
//debugSaveState(basePath: basePath, name: "previous1")
|
||||
//debugRestoreState(basePath: basePath, name: "previous1")
|
||||
debugRestoreState(basePath: basePath, name: "previous1")
|
||||
#endif
|
||||
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
@ -59,6 +59,9 @@ private final class TimeBasedCleanupImpl {
|
||||
public func setMaxStoreTime(_ maxStoreTime: Int32) {
|
||||
if self.maxStoreTime != maxStoreTime {
|
||||
self.maxStoreTime = maxStoreTime
|
||||
#if DEBUG
|
||||
return;
|
||||
#endif
|
||||
self.resetScan(maxStoreTime: maxStoreTime)
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,49 +113,138 @@ func experimentalConvertCompressedLottieToCombinedMp4(data: Data, size: CGSize)
|
||||
|
||||
let singleContext = DrawingContext(size: size, scale: 1.0, clear: true)
|
||||
|
||||
let fps: Int32 = model.framerate?.int32Value ?? 30
|
||||
let frameDuration = CMTimeMake(1, fps)
|
||||
var fps: Int32 = model.framerate?.int32Value ?? 30
|
||||
let _ = fileContext.write(&fps, count: 4)
|
||||
|
||||
var frameData = Data(count: singleContext.length)
|
||||
let frameDataCount = frameData.count
|
||||
if true {
|
||||
let frameLength = singleContext.length
|
||||
assert(frameLength % 16 == 0)
|
||||
|
||||
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZ4))!
|
||||
defer {
|
||||
free(scratchData)
|
||||
}
|
||||
let previousFrameData = malloc(frameLength)!
|
||||
memset(previousFrameData, 0, frameLength)
|
||||
|
||||
while startFrame + currentFrame < endFrame {
|
||||
let lastFrameTime = CMTimeMake(Int64(currentFrame - startFrame), fps)
|
||||
let presentationTime = currentFrame == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration)
|
||||
|
||||
let drawStartTime = CACurrentMediaTime()
|
||||
singleContext.withContext { context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.saveGState()
|
||||
context.scaleBy(x: scale, y: scale)
|
||||
container?.renderFrame(startFrame + currentFrame, in: context)
|
||||
context.restoreGState()
|
||||
}
|
||||
drawingTime += CACurrentMediaTime() - drawStartTime
|
||||
|
||||
let appendStartTime = CACurrentMediaTime()
|
||||
frameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
let length = compression_encode_buffer(bytes, frameDataCount, singleContext.bytes.assumingMemoryBound(to: UInt8.self), singleContext.length, scratchData, COMPRESSION_LZ4)
|
||||
var frameLengthValue: Int32 = Int32(length)
|
||||
let _ = fileContext.write(&frameLengthValue, count: 4)
|
||||
let _ = fileContext.write(bytes, count: length)
|
||||
defer {
|
||||
free(previousFrameData)
|
||||
}
|
||||
|
||||
appendingTime += CACurrentMediaTime() - appendStartTime
|
||||
currentFrame += 1
|
||||
}
|
||||
var compressedFrameData = Data(count: frameLength)
|
||||
let compressedFrameDataLength = compressedFrameData.count
|
||||
|
||||
if startFrame + currentFrame == endFrame {
|
||||
subscriber.putNext(path)
|
||||
subscriber.putCompletion()
|
||||
print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
print("of which drawing time \(drawingTime)")
|
||||
print("of which appending time \(appendingTime)")
|
||||
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZ4))!
|
||||
defer {
|
||||
free(scratchData)
|
||||
}
|
||||
|
||||
while startFrame + currentFrame < endFrame {
|
||||
let drawStartTime = CACurrentMediaTime()
|
||||
singleContext.withContext { context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.saveGState()
|
||||
context.scaleBy(x: scale, y: scale)
|
||||
container?.renderFrame(startFrame + currentFrame, in: context)
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
var lhs = previousFrameData.assumingMemoryBound(to: UInt64.self)
|
||||
var rhs = singleContext.bytes.assumingMemoryBound(to: UInt64.self)
|
||||
for _ in 0 ..< frameLength / 8 {
|
||||
lhs.pointee = rhs.pointee ^ lhs.pointee
|
||||
lhs = lhs.advanced(by: 1)
|
||||
rhs = rhs.advanced(by: 1)
|
||||
}
|
||||
|
||||
drawingTime += CACurrentMediaTime() - drawStartTime
|
||||
|
||||
let appendStartTime = CACurrentMediaTime()
|
||||
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousFrameData.assumingMemoryBound(to: UInt8.self), frameLength, scratchData, COMPRESSION_LZ4)
|
||||
var frameLengthValue: Int32 = Int32(length)
|
||||
let _ = fileContext.write(&frameLengthValue, count: 4)
|
||||
let _ = fileContext.write(bytes, count: length)
|
||||
}
|
||||
|
||||
memcpy(previousFrameData, singleContext.bytes, frameLength)
|
||||
|
||||
appendingTime += CACurrentMediaTime() - appendStartTime
|
||||
currentFrame += 1
|
||||
}
|
||||
|
||||
if startFrame + currentFrame >= endFrame {
|
||||
subscriber.putNext(path)
|
||||
subscriber.putCompletion()
|
||||
print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
print("of which drawing time \(drawingTime)")
|
||||
print("of which appending time \(appendingTime)")
|
||||
}
|
||||
} else {
|
||||
let bgrg422Length = Int(size.width) * 2 * Int(size.height)
|
||||
let aLength = Int(size.width) * Int(size.height)
|
||||
let frameLength = bgrg422Length + aLength
|
||||
|
||||
assert(frameLength % 16 == 0)
|
||||
|
||||
let currentFrameData = malloc(frameLength)!
|
||||
let previousFrameData = malloc(frameLength)!
|
||||
memset(previousFrameData, 0, frameLength)
|
||||
|
||||
defer {
|
||||
free(currentFrameData)
|
||||
free(previousFrameData)
|
||||
}
|
||||
|
||||
let fps: Int32 = model.framerate?.int32Value ?? 30
|
||||
|
||||
var compressedFrameData = Data(count: bgrg422Length + aLength)
|
||||
let compressedFrameDataLength = compressedFrameData.count
|
||||
|
||||
let scratchData = malloc(compression_encode_scratch_buffer_size(COMPRESSION_LZ4))!
|
||||
defer {
|
||||
free(scratchData)
|
||||
}
|
||||
|
||||
while startFrame + currentFrame < endFrame {
|
||||
let drawStartTime = CACurrentMediaTime()
|
||||
singleContext.withContext { context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.saveGState()
|
||||
context.scaleBy(x: scale, y: scale)
|
||||
container?.renderFrame(startFrame + currentFrame, in: context)
|
||||
context.restoreGState()
|
||||
}
|
||||
|
||||
encodeRGBAToBRGR422A(currentFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: 0), currentFrameData.assumingMemoryBound(to: UInt8.self).advanced(by: bgrg422Length), singleContext.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height))
|
||||
|
||||
var lhs = previousFrameData.assumingMemoryBound(to: UInt64.self)
|
||||
var rhs = currentFrameData.assumingMemoryBound(to: UInt64.self)
|
||||
for _ in 0 ..< frameLength / 8 {
|
||||
lhs.pointee = rhs.pointee ^ lhs.pointee
|
||||
lhs = lhs.advanced(by: 1)
|
||||
rhs = rhs.advanced(by: 1)
|
||||
}
|
||||
|
||||
drawingTime += CACurrentMediaTime() - drawStartTime
|
||||
|
||||
let appendStartTime = CACurrentMediaTime()
|
||||
compressedFrameData.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
let length = compression_encode_buffer(bytes, compressedFrameDataLength, previousFrameData.assumingMemoryBound(to: UInt8.self), frameLength, scratchData, COMPRESSION_LZ4)
|
||||
var frameLengthValue: Int32 = Int32(length)
|
||||
let _ = fileContext.write(&frameLengthValue, count: 4)
|
||||
let _ = fileContext.write(bytes, count: length)
|
||||
}
|
||||
|
||||
memcpy(previousFrameData, currentFrameData, frameLength)
|
||||
|
||||
appendingTime += CACurrentMediaTime() - appendStartTime
|
||||
currentFrame += 1
|
||||
}
|
||||
|
||||
if startFrame + currentFrame >= endFrame {
|
||||
subscriber.putNext(path)
|
||||
subscriber.putCompletion()
|
||||
print("animation render time \(CACurrentMediaTime() - startTime)")
|
||||
print("of which drawing time \(drawingTime)")
|
||||
print("of which appending time \(appendingTime)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
submodules/TelegramUI/TelegramUI/AnimationRenderer.swift
Normal file
6
submodules/TelegramUI/TelegramUI/AnimationRenderer.swift
Normal file
@ -0,0 +1,6 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
protocol AnimationRenderer {
|
||||
func render(width: Int, height: Int, bytes: UnsafeRawPointer, length: Int)
|
||||
}
|
||||
@ -217,7 +217,7 @@ final class CachedEmojiRepresentation: CachedMediaResourceRepresentation {
|
||||
|
||||
final class CachedAnimatedStickerRepresentation: CachedMediaResourceRepresentation {
|
||||
var uniqueId: String {
|
||||
return "animated-sticker-v2"
|
||||
return "animated-sticker-v3"
|
||||
}
|
||||
|
||||
func isEqual(to: CachedMediaResourceRepresentation) -> Bool {
|
||||
|
||||
@ -5,50 +5,29 @@ import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AVFoundation
|
||||
import CoreImage
|
||||
import TelegramPresentationData
|
||||
import Compression
|
||||
|
||||
private class AlphaFrameFilter: CIFilter {
|
||||
static var kernel: CIColorKernel? = {
|
||||
return CIColorKernel(source: """
|
||||
kernel vec4 alphaFrame(__sample s, __sample m) {
|
||||
return vec4( s.rgb, 1.0 - m.r );
|
||||
}
|
||||
""")
|
||||
}()
|
||||
private final class AnimationFrameCache {
|
||||
private var cache: [Int: NSPurgeableData] = [:]
|
||||
|
||||
var inputImage: CIImage?
|
||||
var maskImage: CIImage?
|
||||
|
||||
override var outputImage: CIImage? {
|
||||
let kernel = AlphaFrameFilter.kernel!
|
||||
guard let inputImage = inputImage, let maskImage = maskImage else {
|
||||
return nil
|
||||
func get(index: Int, _ f: (NSPurgeableData?) -> Void) {
|
||||
guard let data = self.cache[index] else {
|
||||
f(nil)
|
||||
return
|
||||
}
|
||||
if data.beginContentAccess() {
|
||||
f(data)
|
||||
data.endContentAccess()
|
||||
} else {
|
||||
self.cache.removeValue(forKey: index)
|
||||
f(nil)
|
||||
}
|
||||
let args = [inputImage as AnyObject, maskImage as AnyObject]
|
||||
return kernel.apply(extent: inputImage.extent, arguments: args)
|
||||
}
|
||||
}
|
||||
|
||||
private func createVideoComposition(for playerItem: AVPlayerItem, ready: @escaping () -> Void) -> AVVideoComposition? {
|
||||
let videoSize = CGSize(width: playerItem.presentationSize.width, height: playerItem.presentationSize.height / 2.0)
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
let composition = AVMutableVideoComposition(asset: playerItem.asset, applyingCIFiltersWithHandler: { request in
|
||||
let sourceRect = CGRect(origin: .zero, size: videoSize)
|
||||
let alphaRect = sourceRect.offsetBy(dx: 0, dy: sourceRect.height)
|
||||
let filter = AlphaFrameFilter()
|
||||
filter.inputImage = request.sourceImage.cropped(to: alphaRect)
|
||||
.transformed(by: CGAffineTransform(translationX: 0, y: -sourceRect.height))
|
||||
filter.maskImage = request.sourceImage.cropped(to: sourceRect)
|
||||
request.finish(with: filter.outputImage!, context: nil)
|
||||
ready()
|
||||
})
|
||||
composition.renderSize = videoSize
|
||||
return composition
|
||||
} else {
|
||||
return nil
|
||||
func set(index: Int, bytes: UnsafeRawPointer, length: Int) {
|
||||
self.cache[index] = NSPurgeableData(bytes: bytes, length: length)
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,10 +38,15 @@ private final class StickerAnimationNode: ASDisplayNode {
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
||||
var started: () -> Void = {}
|
||||
private var reportedStarted = false
|
||||
|
||||
private var timer: SwiftSignalKit.Timer?
|
||||
|
||||
var data: Data?
|
||||
private var data: Data?
|
||||
private var frameCache = AnimationFrameCache()
|
||||
|
||||
private var renderer: (AnimationRenderer & ASDisplayNode)?
|
||||
|
||||
var visibility = false {
|
||||
didSet {
|
||||
if self.visibility {
|
||||
@ -83,6 +67,19 @@ private final class StickerAnimationNode: ASDisplayNode {
|
||||
self.timer?.invalidate()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
self.renderer = SoftwareAnimationRenderer()
|
||||
#else
|
||||
self.renderer = SoftwareAnimationRenderer()
|
||||
//self.renderer = MetalAnimationRenderer()
|
||||
#endif
|
||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
self.addSubnode(self.renderer!)
|
||||
}
|
||||
|
||||
func setup(account: Account, fileReference: FileMediaReference) {
|
||||
self.disposable.set(chatMessageAnimationData(postbox: account.postbox, fileReference: fileReference, synchronousLoad: false).start(next: { [weak self] data in
|
||||
if let strongSelf = self, data.complete {
|
||||
@ -108,26 +105,155 @@ private final class StickerAnimationNode: ASDisplayNode {
|
||||
let dataCount = data.count
|
||||
self.timer?.invalidate()
|
||||
var scratchBuffer = Data(count: compression_decode_scratch_buffer_size(COMPRESSION_LZ4))
|
||||
let context = DrawingContext(size: CGSize(width: 400.0, height: 400.0), scale: 1.0, clear: false)
|
||||
|
||||
let width = 320
|
||||
let height = 320
|
||||
|
||||
var offset = 0
|
||||
let timer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
|
||||
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
var frameLength: Int32 = 0
|
||||
memcpy(&frameLength, bytes.advanced(by: offset), 4)
|
||||
scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
compression_decode_buffer(context.bytes.assumingMemoryBound(to: UInt8.self), context.length, bytes.advanced(by: offset + 4), Int(frameLength), UnsafeMutableRawPointer(scratchBytes), COMPRESSION_LZ4)
|
||||
}
|
||||
if let image = context.generateImage() {
|
||||
self?.contents = image.cgImage
|
||||
}
|
||||
offset += 4 + Int(frameLength)
|
||||
if offset == dataCount {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
var fps: Int32 = 0
|
||||
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
memcpy(&fps, bytes, 4)
|
||||
offset += 4
|
||||
}
|
||||
|
||||
if true {
|
||||
var decodeBuffer = Data(count: width * 4 * height)
|
||||
var frameBuffer = Data(count: width * 4 * height)
|
||||
let decodeBufferLength = decodeBuffer.count
|
||||
frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
memset(bytes, 0, decodeBufferLength)
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.timer = timer
|
||||
timer.start()
|
||||
|
||||
var frameIndex = 0
|
||||
let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(fps), repeat: true, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
var frameLength: Int32 = 0
|
||||
memcpy(&frameLength, bytes.advanced(by: offset), 4)
|
||||
|
||||
var usedCache = false
|
||||
strongSelf.frameCache.get(index: frameIndex, { data in
|
||||
if let data = data {
|
||||
usedCache = true
|
||||
|
||||
strongSelf.renderer?.render(width: 320, height: 320, bytes: data.bytes, length: data.length)
|
||||
|
||||
if !strongSelf.reportedStarted {
|
||||
strongSelf.reportedStarted = true
|
||||
strongSelf.started()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if !usedCache {
|
||||
scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
decodeBuffer.withUnsafeMutableBytes { (decodeBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
frameBuffer.withUnsafeMutableBytes { (frameBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
compression_decode_buffer(decodeBytes, decodeBufferLength, bytes.advanced(by: offset + 4), Int(frameLength), UnsafeMutableRawPointer(scratchBytes), COMPRESSION_LZ4)
|
||||
|
||||
var lhs = UnsafeMutableRawPointer(frameBytes).assumingMemoryBound(to: UInt64.self)
|
||||
var rhs = UnsafeRawPointer(decodeBytes).assumingMemoryBound(to: UInt64.self)
|
||||
for _ in 0 ..< decodeBufferLength / 8 {
|
||||
lhs.pointee = lhs.pointee ^ rhs.pointee
|
||||
lhs = lhs.advanced(by: 1)
|
||||
rhs = rhs.advanced(by: 1)
|
||||
}
|
||||
|
||||
strongSelf.renderer?.render(width: 320, height: 320, bytes: frameBytes, length: decodeBufferLength)
|
||||
|
||||
strongSelf.frameCache.set(index: frameIndex, bytes: frameBytes, length: decodeBufferLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !strongSelf.reportedStarted {
|
||||
strongSelf.reportedStarted = true
|
||||
strongSelf.started()
|
||||
}
|
||||
}
|
||||
|
||||
offset += 4 + Int(frameLength)
|
||||
frameIndex += 1
|
||||
if offset == dataCount {
|
||||
offset = 4
|
||||
frameIndex = 0
|
||||
}
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.timer = timer
|
||||
timer.start()
|
||||
} else {
|
||||
var decodeBuffer = Data(count: width * 2 * height + width * height)
|
||||
var frameBuffer = Data(count: width * 2 * height + width * height)
|
||||
let decodeBufferLength = decodeBuffer.count
|
||||
frameBuffer.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
memset(bytes, 0, decodeBufferLength)
|
||||
}
|
||||
|
||||
var frameIndex = 0
|
||||
let timer = SwiftSignalKit.Timer(timeout: 1.0 / Double(offset), repeat: true, completion: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
data.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
var frameLength: Int32 = 0
|
||||
memcpy(&frameLength, bytes.advanced(by: offset), 4)
|
||||
|
||||
var usedCache = false
|
||||
strongSelf.frameCache.get(index: frameIndex, { data in
|
||||
if let data = data {
|
||||
usedCache = true
|
||||
|
||||
strongSelf.renderer?.render(width: 320, height: 320, bytes: data.bytes, length: data.length)
|
||||
|
||||
if !strongSelf.reportedStarted {
|
||||
strongSelf.reportedStarted = true
|
||||
strongSelf.started()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if !usedCache {
|
||||
scratchBuffer.withUnsafeMutableBytes { (scratchBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
decodeBuffer.withUnsafeMutableBytes { (decodeBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
frameBuffer.withUnsafeMutableBytes { (frameBytes: UnsafeMutablePointer<UInt8>) -> Void in
|
||||
compression_decode_buffer(decodeBytes, decodeBufferLength, bytes.advanced(by: offset + 4), Int(frameLength), UnsafeMutableRawPointer(scratchBytes), COMPRESSION_LZ4)
|
||||
|
||||
var lhs = UnsafeMutableRawPointer(frameBytes).assumingMemoryBound(to: UInt64.self)
|
||||
var rhs = UnsafeRawPointer(decodeBytes).assumingMemoryBound(to: UInt64.self)
|
||||
for _ in 0 ..< Int(decodeBufferLength) / 8 {
|
||||
lhs.pointee = lhs.pointee ^ rhs.pointee
|
||||
lhs = lhs.advanced(by: 1)
|
||||
rhs = rhs.advanced(by: 1)
|
||||
}
|
||||
|
||||
strongSelf.renderer?.render(width: 320, height: 320, bytes: frameBytes, length: decodeBufferLength)
|
||||
|
||||
strongSelf.frameCache.set(index: frameIndex, bytes: frameBytes, length: decodeBufferLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !strongSelf.reportedStarted {
|
||||
strongSelf.reportedStarted = true
|
||||
strongSelf.started()
|
||||
}
|
||||
}
|
||||
|
||||
offset += 4 + Int(frameLength)
|
||||
frameIndex += 1
|
||||
if offset == dataCount {
|
||||
offset = 0
|
||||
frameIndex = 0
|
||||
}
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.timer = timer
|
||||
timer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,6 +261,10 @@ private final class StickerAnimationNode: ASDisplayNode {
|
||||
self.timer?.invalidate()
|
||||
self.timer = nil
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
self.renderer?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
}
|
||||
|
||||
class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
@ -213,14 +343,16 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
private var visibilityPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
if self.visibility != oldValue {
|
||||
switch self.visibility {
|
||||
case .visible:
|
||||
self.animationNode.visibility = true
|
||||
self.visibilityPromise.set(true)
|
||||
case .none:
|
||||
self.animationNode.visibility = false
|
||||
self.visibilityPromise.set(false)
|
||||
let wasVisible = oldValue != .none
|
||||
let isVisible = self.visibility != .none
|
||||
|
||||
if wasVisible != isVisible {
|
||||
if isVisible {
|
||||
self.animationNode.visibility = true
|
||||
self.visibilityPromise.set(true)
|
||||
} else {
|
||||
self.animationNode.visibility = false
|
||||
self.visibilityPromise.set(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -255,14 +387,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
|
||||
let incoming = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||
var imageSize: CGSize = CGSize(width: 162.0, height: 162.0)
|
||||
if let telegramFile = telegramFile {
|
||||
var imageSize: CGSize = CGSize(width: 160.0, height: 160.0)
|
||||
/*if let telegramFile = telegramFile {
|
||||
if let dimensions = telegramFile.dimensions {
|
||||
imageSize = dimensions.aspectFitted(displaySize)
|
||||
} else if let thumbnailSize = telegramFile.previewRepresentations.first?.dimensions {
|
||||
imageSize = thumbnailSize.aspectFitted(displaySize)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
let avatarInset: CGFloat
|
||||
var hasAvatar = false
|
||||
@ -434,6 +566,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
strongSelf.imageNode.frame = updatedImageFrame
|
||||
strongSelf.animationNode.frame = updatedImageFrame.insetBy(dx: imageInset, dy: imageInset)
|
||||
strongSelf.animationNode.updateLayout(size: updatedImageFrame.insetBy(dx: imageInset, dy: imageInset).size)
|
||||
imageApply()
|
||||
|
||||
if let updatedShareButtonNode = updatedShareButtonNode {
|
||||
|
||||
@ -236,8 +236,8 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
|
||||
var visibility: ListViewItemNodeVisibility = .none {
|
||||
didSet {
|
||||
self.contentImageNode?.visibility = self.visibility
|
||||
self.contentInstantVideoNode?.visibility = self.visibility
|
||||
self.contentImageNode?.visibility = self.visibility != .none
|
||||
self.contentInstantVideoNode?.visibility = self.visibility != .none
|
||||
}
|
||||
}
|
||||
|
||||
@ -784,7 +784,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
strongSelf.openMedia?(mode)
|
||||
}
|
||||
}
|
||||
contentImageNode.visibility = strongSelf.visibility
|
||||
contentImageNode.visibility = strongSelf.visibility != .none
|
||||
}
|
||||
let _ = contentImageApply(transition, synchronousLoads)
|
||||
let contentImageFrame: CGRect
|
||||
@ -800,7 +800,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
adjustedStatusFrame = CGRect(origin: CGPoint(x: contentImageFrame.width - statusFrame.size.width - 2.0, y: contentImageFrame.height - statusFrame.size.height - 2.0), size: statusFrame.size)
|
||||
}
|
||||
} else if let contentImageNode = strongSelf.contentImageNode {
|
||||
contentImageNode.visibility = .none
|
||||
contentImageNode.visibility = false
|
||||
contentImageNode.removeFromSupernode()
|
||||
strongSelf.contentImageNode = nil
|
||||
}
|
||||
|
||||
@ -39,8 +39,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
if self.visibility != oldValue {
|
||||
self.interactiveVideoNode.visibility = self.visibility
|
||||
let wasVisible = oldValue != .none
|
||||
let isVisible = self.visibility != .none
|
||||
|
||||
if wasVisible != isVisible {
|
||||
self.interactiveVideoNode.visibility = isVisible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,14 +61,14 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
||||
private let fetchedThumbnailDisposable = MetaDisposable()
|
||||
|
||||
private var shouldAcquireVideoContext: Bool {
|
||||
if case .visible = self.visibility {
|
||||
if self.visibility {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var visibility: ListViewItemNodeVisibility = .none {
|
||||
var visibility: Bool = false {
|
||||
didSet {
|
||||
if self.visibility != oldValue {
|
||||
self.videoNode?.canAttachContent = self.shouldAcquireVideoContext
|
||||
|
||||
@ -86,26 +86,21 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
private var secretTimer: SwiftSignalKit.Timer?
|
||||
|
||||
var visibilityPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
var visibility: ListViewItemNodeVisibility = .none {
|
||||
var visibility: Bool = false {
|
||||
didSet {
|
||||
if let videoNode = self.videoNode {
|
||||
switch self.visibility {
|
||||
case .visible:
|
||||
if !videoNode.canAttachContent {
|
||||
videoNode.canAttachContent = true
|
||||
if videoNode.hasAttachedContext {
|
||||
videoNode.play()
|
||||
}
|
||||
if self.visibility {
|
||||
if !videoNode.canAttachContent {
|
||||
videoNode.canAttachContent = true
|
||||
if videoNode.hasAttachedContext {
|
||||
videoNode.play()
|
||||
}
|
||||
case .none:
|
||||
videoNode.canAttachContent = false
|
||||
}
|
||||
} else {
|
||||
videoNode.canAttachContent = false
|
||||
}
|
||||
}
|
||||
var isVisible = false
|
||||
if case .visible = self.visibility {
|
||||
isVisible = true
|
||||
}
|
||||
self.visibilityPromise.set(isVisible)
|
||||
self.visibilityPromise.set(self.visibility)
|
||||
}
|
||||
}
|
||||
|
||||
@ -638,7 +633,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
videoNode.updateLayout(size: arguments.drawingSize, transition: .immediate)
|
||||
videoNode.frame = imageFrame
|
||||
|
||||
if case .visible = strongSelf.visibility {
|
||||
if strongSelf.visibility {
|
||||
if !videoNode.canAttachContent {
|
||||
videoNode.canAttachContent = true
|
||||
if videoNode.hasAttachedContext {
|
||||
|
||||
@ -22,7 +22,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
self.interactiveImageNode.visibility = self.visibility
|
||||
self.interactiveImageNode.visibility = self.visibility != .none
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -889,7 +889,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: 400.0, height: 400.0)).start(next: { path in
|
||||
return experimentalConvertCompressedLottieToCombinedMp4(data: data, size: CGSize(width: 320.0, height: 320.0)).start(next: { path in
|
||||
subscriber.putNext(CachedMediaResourceRepresentationResult(temporaryPath: path))
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
|
||||
@ -105,8 +105,11 @@ class MediaInputPaneTrendingItemNode: ListViewItemNode {
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
if self.visibility != oldValue {
|
||||
if case .visible = self.visibility {
|
||||
let wasVisible = oldValue != .none
|
||||
let isVisible = self.visibility != .none
|
||||
|
||||
if isVisible != wasVisible {
|
||||
if isVisible {
|
||||
if let item = self.item, item.unread {
|
||||
self.readDisposable.set((
|
||||
markFeaturedStickerPacksAsSeenInteractively(postbox: item.account.postbox, ids: [item.info.id])
|
||||
|
||||
177
submodules/TelegramUI/TelegramUI/MetalAnimationRenderer.swift
Normal file
177
submodules/TelegramUI/TelegramUI/MetalAnimationRenderer.swift
Normal file
@ -0,0 +1,177 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Metal
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
|
||||
final class MetalAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
||||
private let device: MTLDevice
|
||||
private let pipelineState: MTLRenderPipelineState
|
||||
private let commandQueue: MTLCommandQueue
|
||||
private let vertexBuffer: MTLBuffer
|
||||
private let colorTexture: MTLTexture
|
||||
private let alphaTexture: MTLTexture
|
||||
private let samplerColor: MTLSamplerState
|
||||
private let samplerAlpha: MTLSamplerState
|
||||
|
||||
private var metalLayer: CAMetalLayer {
|
||||
return self.layer as! CAMetalLayer
|
||||
}
|
||||
|
||||
override init() {
|
||||
let device = MTLCreateSystemDefaultDevice()!
|
||||
|
||||
self.device = device
|
||||
|
||||
do {
|
||||
let library = try device.makeLibrary(source:
|
||||
"""
|
||||
using namespace metal;
|
||||
|
||||
struct VertexIn {
|
||||
packed_float3 position;
|
||||
packed_float2 texCoord;
|
||||
};
|
||||
|
||||
struct VertexOut {
|
||||
float4 position [[position]];
|
||||
float2 texCoord;
|
||||
};
|
||||
|
||||
vertex VertexOut basic_vertex(
|
||||
const device VertexIn* vertex_array [[ buffer(0) ]],
|
||||
unsigned int vid [[ vertex_id ]]
|
||||
) {
|
||||
VertexIn VertexIn = vertex_array[vid];
|
||||
|
||||
VertexOut VertexOut;
|
||||
VertexOut.position = float4(VertexIn.position, 1.0);
|
||||
VertexOut.texCoord = VertexIn.texCoord;
|
||||
|
||||
return VertexOut;
|
||||
}
|
||||
|
||||
fragment float4 basic_fragment(
|
||||
VertexOut interpolated [[stage_in]],
|
||||
texture2d<float> texColor [[ texture(0) ]],
|
||||
sampler samplerColor [[ sampler(0) ]],
|
||||
texture2d<float> texA [[ texture(1) ]],
|
||||
sampler samplerA [[ sampler(1) ]]
|
||||
) {
|
||||
float4 color = texColor.sample(samplerColor, interpolated.texCoord);
|
||||
float4 alpha = texA.sample(samplerA, interpolated.texCoord);
|
||||
return float4(color.r * alpha.a, color.g * alpha.a, color.b * alpha.a, alpha.a);
|
||||
}
|
||||
""", options: nil)
|
||||
|
||||
let fragmentProgram = library.makeFunction(name: "basic_fragment")
|
||||
let vertexProgram = library.makeFunction(name: "basic_vertex")
|
||||
|
||||
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
|
||||
pipelineStateDescriptor.vertexFunction = vertexProgram
|
||||
pipelineStateDescriptor.fragmentFunction = fragmentProgram
|
||||
pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
|
||||
|
||||
self.pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
|
||||
|
||||
self.commandQueue = device.makeCommandQueue()!
|
||||
|
||||
let vertexData: [Float] = [
|
||||
-1.0, -1.0, 0.0, 0.0, 1.0,
|
||||
-1.0, 1.0, 0.0, 0.0, 0.0,
|
||||
1.0, -1.0, 0.0, 1.0, 1.0,
|
||||
1.0, -1.0, 0.0, 1.0, 1.0,
|
||||
-1.0, 1.0, 0.0, 0.0, 0.0,
|
||||
1.0, 1.0, 0.0, 1.0, 0.0
|
||||
]
|
||||
|
||||
let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0])
|
||||
self.vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: [])!
|
||||
|
||||
let colorTextureDesc: MTLTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgrg422, width: 320, height: 320, mipmapped: false)
|
||||
colorTextureDesc.sampleCount = 1
|
||||
if #available(iOS 9.0, *) {
|
||||
colorTextureDesc.storageMode = .private
|
||||
colorTextureDesc.usage = .shaderRead
|
||||
}
|
||||
colorTextureDesc.textureType = .type2D
|
||||
|
||||
self.colorTexture = device.makeTexture(descriptor: colorTextureDesc)!
|
||||
|
||||
let alphaTextureDesc: MTLTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .a8Unorm, width: 320, height: 320, mipmapped: false)
|
||||
alphaTextureDesc.sampleCount = 1
|
||||
if #available(iOS 9.0, *) {
|
||||
alphaTextureDesc.storageMode = .private
|
||||
alphaTextureDesc.usage = .shaderRead
|
||||
}
|
||||
alphaTextureDesc.textureType = .type2D
|
||||
|
||||
self.alphaTexture = device.makeTexture(descriptor: alphaTextureDesc)!
|
||||
|
||||
let sampler = MTLSamplerDescriptor()
|
||||
sampler.minFilter = MTLSamplerMinMagFilter.nearest
|
||||
sampler.magFilter = MTLSamplerMinMagFilter.nearest
|
||||
sampler.mipFilter = MTLSamplerMipFilter.nearest
|
||||
sampler.maxAnisotropy = 1
|
||||
sampler.sAddressMode = MTLSamplerAddressMode.clampToEdge
|
||||
sampler.tAddressMode = MTLSamplerAddressMode.clampToEdge
|
||||
sampler.rAddressMode = MTLSamplerAddressMode.clampToEdge
|
||||
sampler.normalizedCoordinates = true
|
||||
sampler.lodMinClamp = 0.0
|
||||
sampler.lodMaxClamp = .greatestFiniteMagnitude
|
||||
self.samplerColor = device.makeSamplerState(descriptor: sampler)!
|
||||
self.samplerAlpha = device.makeSamplerState(descriptor: sampler)!
|
||||
} catch let e {
|
||||
print(e)
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.setLayerBlock { () -> CALayer in
|
||||
return CAMetalLayer()
|
||||
}
|
||||
|
||||
self.metalLayer.device = self.device
|
||||
self.metalLayer.pixelFormat = .bgra8Unorm
|
||||
self.metalLayer.framebufferOnly = true
|
||||
self.metalLayer.isOpaque = false
|
||||
self.metalLayer.contentsScale = 2.0
|
||||
}
|
||||
|
||||
func render(width: Int, height: Int, bytes: UnsafeRawPointer, length: Int) {
|
||||
if self.metalLayer.bounds.width.isZero {
|
||||
return
|
||||
}
|
||||
|
||||
let bgrgLength = width * 2 * height
|
||||
let alphaLength = width * height
|
||||
|
||||
self.colorTexture.replace(region: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0, withBytes: bytes.assumingMemoryBound(to: UInt8.self), bytesPerRow: width * 2)
|
||||
self.alphaTexture.replace(region: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0, withBytes: bytes.assumingMemoryBound(to: UInt8.self).advanced(by: bgrgLength), bytesPerRow: width)
|
||||
|
||||
let renderPassDescriptor = MTLRenderPassDescriptor()
|
||||
let drawable = self.metalLayer.nextDrawable()!
|
||||
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
|
||||
renderPassDescriptor.colorAttachments[0].loadAction = .clear
|
||||
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
|
||||
|
||||
let commandBuffer = commandQueue.makeCommandBuffer()!
|
||||
|
||||
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!
|
||||
renderEncoder.setRenderPipelineState(self.pipelineState)
|
||||
renderEncoder.setVertexBuffer(self.vertexBuffer, offset: 0, index: 0)
|
||||
renderEncoder.setFragmentTexture(self.colorTexture, index: 0)
|
||||
renderEncoder.setFragmentSamplerState(self.samplerColor, index: 0)
|
||||
renderEncoder.setFragmentTexture(self.alphaTexture, index: 1)
|
||||
renderEncoder.setFragmentSamplerState(self.samplerAlpha, index: 1)
|
||||
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1)
|
||||
renderEncoder.endEncoding()
|
||||
|
||||
commandBuffer.present(drawable)
|
||||
commandBuffer.commit()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,19 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramUIPrivateModule
|
||||
|
||||
final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
||||
func render(width: Int, height: Int, bytes: UnsafeRawPointer, length: Int) {
|
||||
let image = generateImagePixel(CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, pixelGenerator: { _, pixelData in
|
||||
if true {
|
||||
memcpy(pixelData, bytes, length)
|
||||
} else {
|
||||
encodeBRGR422AToRGBA(bytes.assumingMemoryBound(to: UInt8.self), bytes.assumingMemoryBound(to: UInt8.self).advanced(by: width * 2 * height), pixelData, Int32(width), Int32(height))
|
||||
}
|
||||
})
|
||||
|
||||
self.contents = image?.cgImage
|
||||
}
|
||||
}
|
||||
@ -22,4 +22,5 @@ module TelegramUIPrivateModule {
|
||||
header "../TGPresentationAutoNightPreferences.h"
|
||||
header "../TGProxyItem.h"
|
||||
header "../UIImage+ImageEffects.h"
|
||||
header "../YUV.h"
|
||||
}
|
||||
|
||||
7
submodules/TelegramUI/TelegramUI/YUV.h
Normal file
7
submodules/TelegramUI/TelegramUI/YUV.h
Normal file
@ -0,0 +1,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
void encodeRGBAToBRGR422A(uint8_t * _Nonnull bgrg422, uint8_t * _Nonnull a, uint8_t const * _Nonnull argb, int width, int height);
|
||||
void encodeBRGR422AToRGBA(uint8_t const * _Nonnull bgrg422, uint8_t const * _Nonnull a, uint8_t * _Nonnull argb, int width, int height);
|
||||
|
||||
NSData * _Nonnull encodeSparseBuffer(uint8_t const * _Nonnull bytes, int length);
|
||||
void decodeSparseeBuffer(uint8_t * _Nonnull bytes, uint8_t const * _Nonnull buffer);
|
||||
67
submodules/TelegramUI/TelegramUI/YUV.m
Normal file
67
submodules/TelegramUI/TelegramUI/YUV.m
Normal file
@ -0,0 +1,67 @@
|
||||
#import "YUV.h"
|
||||
|
||||
void encodeRGBAToBRGR422A(uint8_t *bgrg422, uint8_t *a, uint8_t const *argb, int width, int height) {
|
||||
int i, j;
|
||||
int lineWidth = width * 2;
|
||||
for (j = 0; j < height; j++) {
|
||||
for (i = 0; i < width; i += 2) {
|
||||
int A1 = argb[(j * width + i) * 4 + 0];
|
||||
int R1 = argb[(j * width + i) * 4 + 3];
|
||||
int G1 = argb[(j * width + i) * 4 + 2];
|
||||
int B1 = argb[(j * width + i) * 4 + 1];
|
||||
|
||||
int A2 = argb[(j * width + i) * 4 + 4];
|
||||
int R2 = argb[(j * width + i) * 4 + 7];
|
||||
int G2 = argb[(j * width + i) * 4 + 6];
|
||||
int B2 = argb[(j * width + i) * 4 + 5];
|
||||
|
||||
bgrg422[j * lineWidth + (i / 2) * 4 + 0] = (uint8_t)((B1 + B2) >> 1);
|
||||
bgrg422[j * lineWidth + (i / 2) * 4 + 1] = G1;
|
||||
bgrg422[j * lineWidth + (i / 2) * 4 + 2] = (uint8_t)((R1 + R2) >> 1);
|
||||
bgrg422[j * lineWidth + (i / 2) * 4 + 3] = G2;
|
||||
|
||||
a[j * width + i + 0] = A1;
|
||||
a[j * width + i + 1] = A2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void encodeBRGR422AToRGBA(uint8_t const * _Nonnull bgrg422, uint8_t const * _Nonnull const a, uint8_t * _Nonnull argb, int width, int height) {
|
||||
int i, j;
|
||||
int lineWidth = width * 2;
|
||||
for (j = 0; j < height; j++) {
|
||||
for (i = 0; i < width; i += 2) {
|
||||
argb[(j * width + i) * 4 + 0] = a[j * width + i + 0];
|
||||
argb[(j * width + i) * 4 + 3] = bgrg422[j * lineWidth + (i / 2) * 4 + 2];
|
||||
argb[(j * width + i) * 4 + 2] = bgrg422[j * lineWidth + (i / 2) * 4 + 1];
|
||||
argb[(j * width + i) * 4 + 1] = bgrg422[j * lineWidth + (i / 2) * 4 + 0];
|
||||
|
||||
argb[(j * width + i) * 4 + 4] = a[j * width + i + 1];
|
||||
argb[(j * width + i) * 4 + 7] = bgrg422[j * lineWidth + (i / 2) * 4 + 2];
|
||||
argb[(j * width + i) * 4 + 6] = bgrg422[j * lineWidth + (i / 2) * 4 + 3];
|
||||
argb[(j * width + i) * 4 + 5] = bgrg422[j * lineWidth + (i / 2) * 4 + 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSData * _Nonnull encodeSparseBuffer(uint8_t const * _Nonnull bytes, int length) {
|
||||
NSMutableData *result = [[NSMutableData alloc] init];
|
||||
int offset = 0;
|
||||
int currentStart = 0;
|
||||
int currentType = 0;
|
||||
while (offset != length) {
|
||||
if (bytes[offset] == 0) {
|
||||
if (currentType != 0) {
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
offset += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void decodeSparseeBuffer(uint8_t * _Nonnull bytes, uint8_t const * _Nonnull buffer) {
|
||||
|
||||
}
|
||||
@ -260,6 +260,11 @@
|
||||
D0147BA7206E8B4F00E40378 /* SecureIdAuthAcceptNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0147BA6206E8B4F00E40378 /* SecureIdAuthAcceptNode.swift */; };
|
||||
D0147BA9206EA35000E40378 /* SecureIdDocumentGalleryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0147BA8206EA35000E40378 /* SecureIdDocumentGalleryController.swift */; };
|
||||
D0147BAB206EA6C100E40378 /* SecureIdDocumentImageGalleryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0147BAA206EA6C100E40378 /* SecureIdDocumentImageGalleryItem.swift */; };
|
||||
D01590A622BD460C0017C33E /* MetalAnimationRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01590A522BD460C0017C33E /* MetalAnimationRenderer.swift */; };
|
||||
D01590A822BD462C0017C33E /* SoftwareAnimationRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01590A722BD462C0017C33E /* SoftwareAnimationRenderer.swift */; };
|
||||
D01590AB22BD467B0017C33E /* AnimationRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01590AA22BD467B0017C33E /* AnimationRenderer.swift */; };
|
||||
D01590AE22BD58AD0017C33E /* YUV.h in Headers */ = {isa = PBXBuildFile; fileRef = D01590AC22BD58AD0017C33E /* YUV.h */; };
|
||||
D01590AF22BD58AD0017C33E /* YUV.m in Sources */ = {isa = PBXBuildFile; fileRef = D01590AD22BD58AD0017C33E /* YUV.m */; };
|
||||
D015E04F225D2E5900CB9E8A /* WebP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04E225D2E5900CB9E8A /* WebP.framework */; };
|
||||
D017734C22049BF800DA06A7 /* UpgradedAccounts.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017734B22049BF800DA06A7 /* UpgradedAccounts.swift */; };
|
||||
D01776B31F1D69A80044446D /* RadialStatusNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01776B21F1D69A80044446D /* RadialStatusNode.swift */; };
|
||||
@ -1456,6 +1461,11 @@
|
||||
D0147BA6206E8B4F00E40378 /* SecureIdAuthAcceptNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdAuthAcceptNode.swift; sourceTree = "<group>"; };
|
||||
D0147BA8206EA35000E40378 /* SecureIdDocumentGalleryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdDocumentGalleryController.swift; sourceTree = "<group>"; };
|
||||
D0147BAA206EA6C100E40378 /* SecureIdDocumentImageGalleryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdDocumentImageGalleryItem.swift; sourceTree = "<group>"; };
|
||||
D01590A522BD460C0017C33E /* MetalAnimationRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalAnimationRenderer.swift; sourceTree = "<group>"; };
|
||||
D01590A722BD462C0017C33E /* SoftwareAnimationRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareAnimationRenderer.swift; sourceTree = "<group>"; };
|
||||
D01590AA22BD467B0017C33E /* AnimationRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationRenderer.swift; sourceTree = "<group>"; };
|
||||
D01590AC22BD58AD0017C33E /* YUV.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YUV.h; sourceTree = "<group>"; };
|
||||
D01590AD22BD58AD0017C33E /* YUV.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = YUV.m; sourceTree = "<group>"; };
|
||||
D015E04E225D2E5900CB9E8A /* WebP.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WebP.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D017494D1E1059570057C89A /* StringWithAppliedEntities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringWithAppliedEntities.swift; sourceTree = "<group>"; };
|
||||
D01749501E1067E40057C89A /* HashtagSearchController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HashtagSearchController.swift; sourceTree = "<group>"; };
|
||||
@ -2788,6 +2798,18 @@
|
||||
name = "Instant Page Gallery";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D01590A922BD46690017C33E /* Animation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D01590AA22BD467B0017C33E /* AnimationRenderer.swift */,
|
||||
D01590A522BD460C0017C33E /* MetalAnimationRenderer.swift */,
|
||||
D01590A722BD462C0017C33E /* SoftwareAnimationRenderer.swift */,
|
||||
D01590AC22BD58AD0017C33E /* YUV.h */,
|
||||
D01590AD22BD58AD0017C33E /* YUV.m */,
|
||||
);
|
||||
name = Animation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D017494F1E1067C00057C89A /* Hashtag Search */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -4494,6 +4516,7 @@
|
||||
D0F69E181D6B8AD10046BCD6 /* Items */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D01590A922BD46690017C33E /* Animation */,
|
||||
D0F69E1B1D6B8B030046BCD6 /* ChatMessageActionItemNode.swift */,
|
||||
D0F69E1C1D6B8B030046BCD6 /* ChatMessageAvatarAccessoryItem.swift */,
|
||||
D0F69E1D1D6B8B030046BCD6 /* ChatMessageBubbleContentCalclulateImageCorners.swift */,
|
||||
@ -4867,6 +4890,7 @@
|
||||
D08803C51F6064CF00DD7951 /* TelegramUI.h in Headers */,
|
||||
D0E9BA171F05574500F079A4 /* STPPaymentCardTextFieldViewModel.h in Headers */,
|
||||
D0EB42001F30ED4F00838FE6 /* LegacyImageProcessors.h in Headers */,
|
||||
D01590AE22BD58AD0017C33E /* YUV.h in Headers */,
|
||||
D008177B22B46B7E008A895F /* TGContactModel.h in Headers */,
|
||||
D0E9BA291F0557A600F079A4 /* STPFormEncodable.h in Headers */,
|
||||
D0E9BA141F05574500F079A4 /* STPCardValidationState.h in Headers */,
|
||||
@ -5314,6 +5338,7 @@
|
||||
09C9EA3821A044B500E90146 /* StringForDuration.swift in Sources */,
|
||||
D0EC6D241EB9F58800EBF1C3 /* CachedResourceRepresentations.swift in Sources */,
|
||||
09619B8E21A34C0100493558 /* InstantPageScrollableNode.swift in Sources */,
|
||||
D01590A622BD460C0017C33E /* MetalAnimationRenderer.swift in Sources */,
|
||||
D01BAA201ECC9A2500295217 /* CallListNodeLocation.swift in Sources */,
|
||||
D0EC6D251EB9F58800EBF1C3 /* FetchCachedRepresentations.swift in Sources */,
|
||||
D0EC6D261EB9F58800EBF1C3 /* TransformOutgoingMessageMedia.swift in Sources */,
|
||||
@ -5509,6 +5534,7 @@
|
||||
D0EC6D811EB9F58800EBF1C3 /* ChatController.swift in Sources */,
|
||||
D0FFF7F81F55B83600BEBC01 /* InstantPageAudioNode.swift in Sources */,
|
||||
D0B37C5E1F8D26A8004252DF /* ThemeSettingsChatPreviewItem.swift in Sources */,
|
||||
D01590A822BD462C0017C33E /* SoftwareAnimationRenderer.swift in Sources */,
|
||||
D093D7DB2062CFF500BC3599 /* SecureIdAuthFormContentNode.swift in Sources */,
|
||||
D0EC6D821EB9F58800EBF1C3 /* ChatControllerInteraction.swift in Sources */,
|
||||
D0EC6D831EB9F58800EBF1C3 /* ChatControllerNode.swift in Sources */,
|
||||
@ -5542,6 +5568,7 @@
|
||||
D0AEAE252080D6830013176E /* PaneSearchContainerNode.swift in Sources */,
|
||||
D01DBA9B209CC6AD00C64E64 /* ChatLinkPreview.swift in Sources */,
|
||||
D044A0FB20BDC40C00326FAC /* CachedChannelAdmins.swift in Sources */,
|
||||
D01590AF22BD58AD0017C33E /* YUV.m in Sources */,
|
||||
D0EC6D901EB9F58900EBF1C3 /* ChatMessageBubbleContentNode.swift in Sources */,
|
||||
09874E582107A4C300E190B8 /* VimeoEmbedImplementation.swift in Sources */,
|
||||
D0EC6D911EB9F58900EBF1C3 /* ChatMessageBubbleItemNode.swift in Sources */,
|
||||
@ -5986,6 +6013,7 @@
|
||||
D0AB262F21C3D3DE008F6685 /* CreatePollController.swift in Sources */,
|
||||
D0EC6E581EB9F58900EBF1C3 /* PeerSelectionController.swift in Sources */,
|
||||
D093D7D92062A9CA00BC3599 /* SecureIdAuthControllerState.swift in Sources */,
|
||||
D01590AB22BD467B0017C33E /* AnimationRenderer.swift in Sources */,
|
||||
D0EC6E591EB9F58900EBF1C3 /* PeerSelectionControllerNode.swift in Sources */,
|
||||
D0EC6E5B1EB9F58900EBF1C3 /* CallController.swift in Sources */,
|
||||
D0AB262921C307D7008F6685 /* ChatMessagePollBubbleContentNode.swift in Sources */,
|
||||
@ -6180,6 +6208,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -6377,6 +6406,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -6619,6 +6649,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -6736,6 +6767,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -6814,6 +6846,7 @@
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_INSTALL_OBJC_HEADER = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
USER_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/third-party/FFmpeg-iOS/include";
|
||||
};
|
||||
@ -6870,6 +6903,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = NO;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user