diff --git a/Tests/AnimationCacheTest/BUILD b/Tests/AnimationCacheTest/BUILD index 7649ea11df..b42fca4697 100644 --- a/Tests/AnimationCacheTest/BUILD +++ b/Tests/AnimationCacheTest/BUILD @@ -31,6 +31,8 @@ swift_library( "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Display:Display", "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", + "//submodules/TelegramUI/Components/VideoAnimationCache:VideoAnimationCache", + "//submodules/TelegramUI/Components/LottieAnimationCache:LottieAnimationCache", "//submodules/rlottie:RLottieBinding", ], ) diff --git a/Tests/AnimationCacheTest/Resources/sticker.webm b/Tests/AnimationCacheTest/Resources/sticker.webm new file mode 100644 index 0000000000..4d01aa5b8e Binary files /dev/null and b/Tests/AnimationCacheTest/Resources/sticker.webm differ diff --git a/Tests/AnimationCacheTest/Sources/ViewController.swift b/Tests/AnimationCacheTest/Sources/ViewController.swift index 964b832277..bfd2798873 100644 --- a/Tests/AnimationCacheTest/Sources/ViewController.swift +++ b/Tests/AnimationCacheTest/Sources/ViewController.swift @@ -2,9 +2,10 @@ import Foundation import UIKit import Display -import RLottieBinding import AnimationCache import SwiftSignalKit +import VideoAnimationCache +import LottieAnimationCache public final class ViewController: UIViewController { private var imageView: UIImageView? @@ -14,6 +15,7 @@ public final class ViewController: UIViewController { private var animationCacheItem: AnimationCacheItem? //private let playbackSize = CGSize(width: 512, height: 512) + //private let playbackSize = CGSize(width: 256, height: 256) private let playbackSize = CGSize(width: 48.0, height: 48.0) //private let playbackSize = CGSize(width: 16, height: 16) @@ -52,26 +54,19 @@ public final class ViewController: UIViewController { return basePath + "/\(Int64.random(in: 0 ... Int64.max))" }) - let path = Bundle.main.path(forResource: "Test2", ofType: "json")! - let data = try! Data(contentsOf: URL(fileURLWithPath: path)) + let path = Bundle.main.path(forResource: "sticker", ofType: "webm")! let scaledSize = CGSize(width: self.playbackSize.width * 2.0, height: self.playbackSize.height * 2.0) - let _ = (self.cache!.get(sourceId: "Item\(Int64.random(in: 0 ... Int64.max))", size: scaledSize, fetch: { size, writer in - writer.queue.async { - let lottieInstance = LottieInstance(data: data, fitzModifier: .none, colorReplacements: nil, cacheKey: "")! - - for i in 0 ..< min(600, Int(lottieInstance.frameCount)) { - //for _ in 0 ..< 10 { - writer.add(with: { surface in - let _ = i - lottieInstance.renderFrame(with: Int32(i), into: surface.argb, width: Int32(surface.width), height: Int32(surface.height), bytesPerRow: Int32(surface.bytesPerRow)) - - return 1.0 / 60.0 - }, proposedWidth: Int(scaledSize.width), proposedHeight: Int(scaledSize.height), insertKeyframe: false) - //} + let _ = (self.cache!.get(sourceId: "Item\(Int64.random(in: 0 ... Int64.max))", size: scaledSize, fetch: { options in + options.writer.queue.async { + if path.hasSuffix(".webm") { + cacheVideoAnimation(path: path, width: Int(options.size.width), height: Int(options.size.height), writer: options.writer, firstFrameOnly: options.firstFrameOnly) + } else { + let data = try! Data(contentsOf: URL(fileURLWithPath: path)) + cacheLottieAnimation(data: data, width: Int(options.size.width), height: Int(options.size.height), keyframeOnly: false, writer: options.writer, firstFrameOnly: options.firstFrameOnly) } - writer.finish() + options.writer.finish() } return EmptyDisposable @@ -107,12 +102,12 @@ public final class ViewController: UIViewController { self.fpsCount += 1 - /*DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0 / 60.0, execute: { [weak self] in + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0 / 30.0, execute: { [weak self] in self?.updateImage() - })*/ - DispatchQueue.main.async { + }) + /*DispatchQueue.main.async { self.updateImage() - } + }*/ default: break } diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h index cd81059078..f935e3cafd 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/PublicHeaders/ImageDCT/YuvConversion.h @@ -11,8 +11,12 @@ void splitRGBAIntoYUVAPlanes(uint8_t const *argb, uint8_t *outY, uint8_t *outU, void combineYUVAPlanesIntoARGB(uint8_t *argb, uint8_t const *inY, uint8_t const *inU, uint8_t const *inV, uint8_t const *inA, int width, int height, int bytesPerRow); void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outBytesPerRow, uint8_t const *inPlane, int inWidth, int inHeight, int inBytesPerRow); -void subtractArraysInt16(int16_t const *a, int16_t const *b, uint16_t *dest, int length); +void convertUInt8toInt16(uint8_t const *source, int16_t *dest, int length); +void convertInt16toUInt8(int16_t const *source, uint8_t *dest, int length); +void subtractArraysInt16(int16_t const *a, int16_t const *b, int16_t *dest, int length); +void addArraysInt16(int16_t const *a, int16_t const *b, int16_t *dest, int length); void subtractArraysUInt8Int16(uint8_t const *a, int16_t const *b, uint8_t *dest, int length); +void addArraysUInt8Int16(uint8_t const *a, int16_t const *b, uint8_t *dest, int length); #ifdef __cplusplus__ } diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp index 9775f1ee66..41d11043e3 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/DCT.cpp @@ -752,8 +752,8 @@ inline void vpx_idct4x4_16_add_neon(const int16x8_t &top64, const int16x8_t &bot vst1_s16(dest, vget_low_s16(a[1])); } -static int dct4x4QuantDC = 60; -static int dct4x4QuantAC = 60; +static int dct4x4QuantDC = 58; +static int dct4x4QuantAC = 58; void performForward4x4Dct(int16_t const *normalizedCoefficients, int16_t *coefficients, int width, int height, DCTELEM *divisors) { DCTELEM block[4 * 4]; @@ -804,7 +804,6 @@ void performForward4x4Dct(int16_t const *normalizedCoefficients, int16_t *coeffi } void performInverse4x4Dct(int16_t const * coefficients, int16_t *normalizedCoefficients, int width, int height, DctAuxiliaryData *auxiliaryData, IFAST_MULT_TYPE *ifmtbl) { - //DCTELEM coefficientBlock[4 * 4]; DCTELEM resultBlock[4 * 4]; for (int y = 0; y < height; y += 4) { @@ -830,11 +829,16 @@ void performInverse4x4Dct(int16_t const * coefficients, int16_t *normalizedCoeff int16x8_t top64 = vreinterpretq_s16_u16(qtop16); int16x8_t bottom64 = vreinterpretq_s16_u16(qbottom16); - /*for (int blockY = 0; blockY < 4; blockY++) { + /*DCTELEM coefficientBlock[4 * 4]; + + for (int blockY = 0; blockY < 4; blockY++) { for (int blockX = 0; blockX < 4; blockX++) { coefficientBlock[zigZag4x4Inv[blockY * 4 + blockX]] = coefficients[(y + blockY) * width + (x + blockX)]; } - }*/ + } + + top64 = vreinterpretq_s16_u64(vld1q_u64((uint64_t *)&coefficientBlock[0])); + bottom64 = vreinterpretq_s16_u64(vld1q_u64((uint64_t *)&coefficientBlock[8]));*/ vpx_idct4x4_16_add_neon(top64, bottom64, resultBlock, dct4x4QuantAC); @@ -848,11 +852,11 @@ void performInverse4x4Dct(int16_t const * coefficients, int16_t *normalizedCoeff vst1_u32((uint32_t *)&normalizedCoefficients[(y + 2) * width + x], c); vst1_u32((uint32_t *)&normalizedCoefficients[(y + 3) * width + x], d); - for (int blockY = 0; blockY < 4; blockY++) { + /*for (int blockY = 0; blockY < 4; blockY++) { for (int blockX = 0; blockX < 4; blockX++) { - //normalizedCoefficients[(y + blockY) * width + (x + blockX)] = resultBlock[blockY * 4 + blockX]; + normalizedCoefficients[(y + blockY) * width + (x + blockX)] = resultBlock[blockY * 4 + blockX]; } - } + }*/ } } } diff --git a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m index dd7123e2ce..0f2532e212 100644 --- a/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m +++ b/submodules/TelegramUI/Components/AnimationCache/ImageDCT/Sources/YuvConversion.m @@ -117,7 +117,42 @@ void scaleImagePlane(uint8_t *outPlane, int outWidth, int outHeight, int outByte vImageScale_Planar8(&src, &dst, nil, kvImageDoNotTile); } -void subtractArraysInt16(int16_t const *a, int16_t const *b, uint16_t *dest, int length) { +void convertUInt8toInt16(uint8_t const *source, int16_t *dest, int length) { + for (int i = 0; i < length; i += 8) { + uint8x8_t lhs8 = vld1_u8(&source[i]); + int16x8_t lhs = vreinterpretq_s16_u16(vmovl_u8(lhs8)); + + vst1q_s16(&dest[i], lhs); + } + if (length % 8 != 0) { + for (int i = length - (length % 8); i < length; i++) { + dest[i] = (int16_t)source[i]; + } + } +} + +void convertInt16toUInt8(int16_t const *source, uint8_t *dest, int length) { + for (int i = 0; i < length; i += 8) { + int16x8_t lhs16 = vld1q_s16(&source[i]); + int8x8_t lhs = vqmovun_s16(lhs16); + + vst1_u8(&dest[i], lhs); + } + if (length % 8 != 0) { + for (int i = length - (length % 8); i < length; i++) { + int16_t result = source[i]; + if (result < 0) { + result = 0; + } + if (result > 255) { + result = 255; + } + dest[i] = (int8_t)result; + } + } +} + +void subtractArraysInt16(int16_t const *a, int16_t const *b, int16_t *dest, int length) { for (int i = 0; i < length; i += 8) { int16x8_t lhs = vld1q_s16((int16_t *)&a[i]); int16x8_t rhs = vld1q_s16((int16_t *)&b[i]); @@ -131,6 +166,20 @@ void subtractArraysInt16(int16_t const *a, int16_t const *b, uint16_t *dest, int } } +void addArraysInt16(int16_t const *a, int16_t const *b, int16_t *dest, int length) { + for (int i = 0; i < length; i += 8) { + int16x8_t lhs = vld1q_s16((int16_t *)&a[i]); + int16x8_t rhs = vld1q_s16((int16_t *)&b[i]); + int16x8_t result = vaddq_s16(lhs, rhs); + vst1q_s16((int16_t *)&dest[i], result); + } + if (length % 8 != 0) { + for (int i = length - (length % 8); i < length; i++) { + dest[i] = a[i] - b[i]; + } + } +} + void subtractArraysUInt8Int16(uint8_t const *a, int16_t const *b, uint8_t *dest, int length) { for (int i = 0; i < length; i += 8) { uint8x8_t lhs8 = vld1_u8(&a[i]); @@ -155,3 +204,41 @@ void subtractArraysUInt8Int16(uint8_t const *a, int16_t const *b, uint8_t *dest, } } } + +void addArraysUInt8Int16(uint8_t const *a, int16_t const *b, uint8_t *dest, int length) { +#if false + for (int i = 0; i < length; i++) { + int16_t result = ((int16_t)a[i]) + b[i]; + if (result < 0) { + result = 0; + } + if (result > 255) { + result = 255; + } + dest[i] = (int8_t)result; + } +#else + for (int i = 0; i < length; i += 8) { + uint8x8_t lhs8 = vld1_u8(&a[i]); + int16x8_t lhs = vreinterpretq_s16_u16(vmovl_u8(lhs8)); + + int16x8_t rhs = vld1q_s16((int16_t *)&b[i]); + int16x8_t result = vaddq_s16(lhs, rhs); + + uint8x8_t result8 = vqmovun_s16(result); + vst1_u8(&dest[i], result8); + } + if (length % 8 != 0) { + for (int i = length - (length % 8); i < length; i++) { + int16_t result = ((int16_t)a[i]) + b[i]; + if (result < 0) { + result = 0; + } + if (result > 255) { + result = 255; + } + dest[i] = (int8_t)result; + } + } +#endif +} diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift index eba675200b..b278c3fae7 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/AnimationCache.swift @@ -246,7 +246,7 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter { private var currentSurface: ImageARGB? private var currentYUVASurface: ImageYUVA420? private var currentFrameFloat: FloatCoefficientsYUVA420? - private var previousFrameFloat: FloatCoefficientsYUVA420? + private var previousFrameCoefficients: DctCoefficientsYUVA420? private var deltaFrameFloat: FloatCoefficientsYUVA420? private var previousYUVASurface: ImageYUVA420? private var currentDctData: DctData? @@ -281,42 +281,47 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter { } func add(with drawingBlock: (AnimationCacheItemDrawingSurface) -> Double?, proposedWidth: Int, proposedHeight: Int, insertKeyframe: Bool) { - let width = roundUp(proposedWidth, multiple: 16) - let height = roundUp(proposedHeight, multiple: 16) - - let surface: ImageARGB - if let current = self.currentSurface { - if current.argbPlane.width == width && current.argbPlane.height == height { - surface = current - } else { - self.isFailed = true - return - } - } else { - surface = ImageARGB(width: width, height: height, rowAlignment: 32) - self.currentSurface = surface - } - - let duration = surface.argbPlane.data.withUnsafeMutableBytes { bytes -> Double? in - return drawingBlock(AnimationCacheItemDrawingSurface( - argb: bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), - width: width, - height: height, - bytesPerRow: surface.argbPlane.bytesPerRow, - length: bytes.count - )) - } - - guard let duration = duration else { - return - } - do { - try addInternal(with: { yuvaSurface in - surface.toYUVA420(target: yuvaSurface) + try self.lock.throwingLocked { + let width = roundUp(proposedWidth, multiple: 16) + let height = roundUp(proposedHeight, multiple: 16) - return duration - }, width: width, height: height, insertKeyframe: insertKeyframe) + let surface: ImageARGB + if let current = self.currentSurface { + if current.argbPlane.width == width && current.argbPlane.height == height { + surface = current + surface.argbPlane.data.withUnsafeMutableBytes { bytes -> Void in + memset(bytes.baseAddress!, 0, bytes.count) + } + } else { + self.isFailed = true + return + } + } else { + surface = ImageARGB(width: width, height: height, rowAlignment: 32) + self.currentSurface = surface + } + + let duration = surface.argbPlane.data.withUnsafeMutableBytes { bytes -> Double? in + return drawingBlock(AnimationCacheItemDrawingSurface( + argb: bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), + width: width, + height: height, + bytesPerRow: surface.argbPlane.bytesPerRow, + length: bytes.count + )) + } + + guard let duration = duration else { + return + } + + try addInternal(with: { yuvaSurface in + surface.toYUVA420(target: yuvaSurface) + + return duration + }, width: width, height: height, insertKeyframe: insertKeyframe) + } } catch { } } @@ -326,9 +331,11 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter { let height = roundUp(proposedHeight, multiple: 16) do { - try addInternal(with: { yuvaSurface in - return drawingBlock(yuvaSurface) - }, width: width, height: height, insertKeyframe: insertKeyframe) + try self.lock.throwingLocked { + try addInternal(with: { yuvaSurface in + return drawingBlock(yuvaSurface) + }, width: width, height: height, insertKeyframe: insertKeyframe) + } } catch { } } @@ -342,202 +349,194 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter { throw WriteError.generic } - try self.lock.throwingLocked { - guard !self.isFailed, !self.isFinished, let file = self.file, let compressedWriter = self.compressedWriter else { - throw WriteError.generic - } - - var isFirstFrame = false - - let yuvaSurface: ImageYUVA420 - if let current = self.currentYUVASurface { - if current.yPlane.width == width && current.yPlane.height == height { - yuvaSurface = current - } else { - self.isFailed = true - throw WriteError.generic - } + guard !self.isFailed, !self.isFinished, let file = self.file, let compressedWriter = self.compressedWriter else { + throw WriteError.generic + } + + var isFirstFrame = false + + let yuvaSurface: ImageYUVA420 + if let current = self.currentYUVASurface { + if current.yPlane.width == width && current.yPlane.height == height { + yuvaSurface = current } else { - isFirstFrame = true - - yuvaSurface = ImageYUVA420(width: width, height: height, rowAlignment: nil) - self.currentYUVASurface = yuvaSurface - } - - let currentFrameFloat: FloatCoefficientsYUVA420 - if let current = self.currentFrameFloat { - if current.yPlane.width == width && current.yPlane.height == height { - currentFrameFloat = current - } else { - self.isFailed = true - throw WriteError.generic - } - } else { - currentFrameFloat = FloatCoefficientsYUVA420(width: width, height: height) - self.currentFrameFloat = currentFrameFloat - } - - let previousFrameFloat: FloatCoefficientsYUVA420 - if let current = self.previousFrameFloat { - if current.yPlane.width == width && current.yPlane.height == height { - previousFrameFloat = current - } else { - self.isFailed = true - throw WriteError.generic - } - } else { - previousFrameFloat = FloatCoefficientsYUVA420(width: width, height: height) - self.previousFrameFloat = previousFrameFloat - } - - let deltaFrameFloat: FloatCoefficientsYUVA420 - if let current = self.deltaFrameFloat { - if current.yPlane.width == width && current.yPlane.height == height { - deltaFrameFloat = current - } else { - self.isFailed = true - throw WriteError.generic - } - } else { - deltaFrameFloat = FloatCoefficientsYUVA420(width: width, height: height) - self.deltaFrameFloat = deltaFrameFloat - } - - let dctData: DctData - if let current = self.currentDctData { - dctData = current - } else { - dctData = DctData(generatingTablesAtQualityLuma: self.dctQualityLuma, chroma: self.dctQualityChroma, delta: self.dctQualityDelta) - self.currentDctData = dctData - } - - let duration = drawingBlock(yuvaSurface) - - guard let duration = duration else { - return - } - - let dctCoefficients: DctCoefficientsYUVA420 - if let current = self.currentDctCoefficients { - if current.yPlane.width == width && current.yPlane.height == height { - dctCoefficients = current - } else { - self.isFailed = true - throw WriteError.generic - } - } else { - dctCoefficients = DctCoefficientsYUVA420(width: width, height: height) - self.currentDctCoefficients = dctCoefficients - } - - let differenceCoefficients: DctCoefficientsYUVA420 - if let current = self.differenceCoefficients { - if current.yPlane.width == width && current.yPlane.height == height { - differenceCoefficients = current - } else { - self.isFailed = true - throw WriteError.generic - } - } else { - differenceCoefficients = DctCoefficientsYUVA420(width: width, height: height) - self.differenceCoefficients = differenceCoefficients - } - - #if DEBUG && false - var insertKeyframe = insertKeyframe - insertKeyframe = true - #endif - - let isKeyframe: Bool - if !isFirstFrame && !insertKeyframe { - isKeyframe = false - - yuvaSurface.toCoefficients(target: currentFrameFloat) - - currentFrameFloat.copy(into: deltaFrameFloat) - - deltaFrameFloat.subtract(other: previousFrameFloat) - - deltaFrameFloat.toDctCoefficients(target: differenceCoefficients) - - differenceCoefficients.dct(dctData: dctData, target: dctCoefficients) - - dctCoefficients.idct(dctData: dctData, target: differenceCoefficients) - - differenceCoefficients.toFloatCoefficients(target: currentFrameFloat) - currentFrameFloat.subtract(other: previousFrameFloat) - currentFrameFloat.clamp() - currentFrameFloat.copy(into: previousFrameFloat) - } else { - isKeyframe = true - - yuvaSurface.dct(dctData: dctData, target: dctCoefficients) - - let previousYUVASurface: ImageYUVA420 - if let current = self.previousYUVASurface { - previousYUVASurface = current - } else { - previousYUVASurface = ImageYUVA420(width: dctCoefficients.yPlane.width, height: dctCoefficients.yPlane.height, rowAlignment: nil) - self.previousYUVASurface = previousYUVASurface - } - dctCoefficients.idct(dctData: dctData, target: previousYUVASurface) - previousYUVASurface.toCoefficients(target: previousFrameFloat) - } - - if isFirstFrame { - file.write(5 as UInt32) - - file.write(UInt32(dctCoefficients.yPlane.width)) - file.write(UInt32(dctCoefficients.yPlane.height)) - - let lumaDctTable = dctData.lumaTable.serializedData() - file.write(UInt32(lumaDctTable.count)) - let _ = file.write(lumaDctTable) - - let chromaDctTable = dctData.chromaTable.serializedData() - file.write(UInt32(chromaDctTable.count)) - let _ = file.write(chromaDctTable) - - let deltaDctTable = dctData.deltaTable.serializedData() - file.write(UInt32(deltaDctTable.count)) - let _ = file.write(deltaDctTable) - - self.contentLengthOffset = Int(file.position()) - file.write(0 as UInt32) - } - - do { - let frameLength = dctCoefficients.yPlane.data.count + dctCoefficients.uPlane.data.count + dctCoefficients.vPlane.data.count + dctCoefficients.aPlane.data.count - try compressedWriter.writeUInt32(UInt32(frameLength)) - - try compressedWriter.writeUInt32(isKeyframe ? 1 : 0) - - for i in 0 ..< 4 { - let dctPlane: DctCoefficientPlane - switch i { - case 0: - dctPlane = dctCoefficients.yPlane - case 1: - dctPlane = dctCoefficients.uPlane - case 2: - dctPlane = dctCoefficients.vPlane - case 3: - dctPlane = dctCoefficients.aPlane - default: - preconditionFailure() - } - - try compressedWriter.writeUInt32(UInt32(dctPlane.data.count)) - try dctPlane.data.withUnsafeBytes { bytes in - try compressedWriter.write(bytes: bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: bytes.count) - } - } - - self.frames.append(FrameMetadata(duration: duration)) - } catch { self.isFailed = true throw WriteError.generic } + } else { + isFirstFrame = true + + yuvaSurface = ImageYUVA420(width: width, height: height, rowAlignment: nil) + self.currentYUVASurface = yuvaSurface + } + + let currentFrameFloat: FloatCoefficientsYUVA420 + if let current = self.currentFrameFloat { + if current.yPlane.width == width && current.yPlane.height == height { + currentFrameFloat = current + } else { + self.isFailed = true + throw WriteError.generic + } + } else { + currentFrameFloat = FloatCoefficientsYUVA420(width: width, height: height) + self.currentFrameFloat = currentFrameFloat + } + + let previousFrameCoefficients: DctCoefficientsYUVA420 + if let current = self.previousFrameCoefficients { + if current.yPlane.width == width && current.yPlane.height == height { + previousFrameCoefficients = current + } else { + self.isFailed = true + throw WriteError.generic + } + } else { + previousFrameCoefficients = DctCoefficientsYUVA420(width: width, height: height) + self.previousFrameCoefficients = previousFrameCoefficients + } + + let deltaFrameFloat: FloatCoefficientsYUVA420 + if let current = self.deltaFrameFloat { + if current.yPlane.width == width && current.yPlane.height == height { + deltaFrameFloat = current + } else { + self.isFailed = true + throw WriteError.generic + } + } else { + deltaFrameFloat = FloatCoefficientsYUVA420(width: width, height: height) + self.deltaFrameFloat = deltaFrameFloat + } + + let dctData: DctData + if let current = self.currentDctData { + dctData = current + } else { + dctData = DctData(generatingTablesAtQualityLuma: self.dctQualityLuma, chroma: self.dctQualityChroma, delta: self.dctQualityDelta) + self.currentDctData = dctData + } + + let duration = drawingBlock(yuvaSurface) + + guard let duration = duration else { + return + } + + let dctCoefficients: DctCoefficientsYUVA420 + if let current = self.currentDctCoefficients { + if current.yPlane.width == width && current.yPlane.height == height { + dctCoefficients = current + } else { + self.isFailed = true + throw WriteError.generic + } + } else { + dctCoefficients = DctCoefficientsYUVA420(width: width, height: height) + self.currentDctCoefficients = dctCoefficients + } + + let differenceCoefficients: DctCoefficientsYUVA420 + if let current = self.differenceCoefficients { + if current.yPlane.width == width && current.yPlane.height == height { + differenceCoefficients = current + } else { + self.isFailed = true + throw WriteError.generic + } + } else { + differenceCoefficients = DctCoefficientsYUVA420(width: width, height: height) + self.differenceCoefficients = differenceCoefficients + } + + #if DEBUG && false + var insertKeyframe = insertKeyframe + insertKeyframe = true + #endif + + let previousYUVASurface: ImageYUVA420 + if let current = self.previousYUVASurface { + previousYUVASurface = current + } else { + previousYUVASurface = ImageYUVA420(width: dctCoefficients.yPlane.width, height: dctCoefficients.yPlane.height, rowAlignment: nil) + self.previousYUVASurface = previousYUVASurface + } + + let isKeyframe: Bool + if !isFirstFrame && !insertKeyframe { + isKeyframe = false + + //previous + delta = current + //delta = current - previous + yuvaSurface.toCoefficients(target: differenceCoefficients) + differenceCoefficients.subtract(other: previousFrameCoefficients) + differenceCoefficients.dct4x4(dctData: dctData, target: dctCoefficients) + + //previous + delta = current + dctCoefficients.idct4x4(dctData: dctData, target: differenceCoefficients) + previousFrameCoefficients.add(other: differenceCoefficients) + } else { + isKeyframe = true + + yuvaSurface.dct8x8(dctData: dctData, target: dctCoefficients) + + dctCoefficients.idct8x8(dctData: dctData, target: yuvaSurface) + yuvaSurface.toCoefficients(target: previousFrameCoefficients) + } + + if isFirstFrame { + file.write(6 as UInt32) + + file.write(UInt32(dctCoefficients.yPlane.width)) + file.write(UInt32(dctCoefficients.yPlane.height)) + + let lumaDctTable = dctData.lumaTable.serializedData() + file.write(UInt32(lumaDctTable.count)) + let _ = file.write(lumaDctTable) + + let chromaDctTable = dctData.chromaTable.serializedData() + file.write(UInt32(chromaDctTable.count)) + let _ = file.write(chromaDctTable) + + let deltaDctTable = dctData.deltaTable.serializedData() + file.write(UInt32(deltaDctTable.count)) + let _ = file.write(deltaDctTable) + + self.contentLengthOffset = Int(file.position()) + file.write(0 as UInt32) + } + + do { + let frameLength = dctCoefficients.yPlane.data.count + dctCoefficients.uPlane.data.count + dctCoefficients.vPlane.data.count + dctCoefficients.aPlane.data.count + try compressedWriter.writeUInt32(UInt32(frameLength)) + + try compressedWriter.writeUInt32(isKeyframe ? 1 : 0) + + for i in 0 ..< 4 { + let dctPlane: DctCoefficientPlane + switch i { + case 0: + dctPlane = dctCoefficients.yPlane + case 1: + dctPlane = dctCoefficients.uPlane + case 2: + dctPlane = dctCoefficients.vPlane + case 3: + dctPlane = dctCoefficients.aPlane + default: + preconditionFailure() + } + + try compressedWriter.writeUInt32(UInt32(dctPlane.data.count)) + try dctPlane.data.withUnsafeBytes { bytes in + try compressedWriter.write(bytes: bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: bytes.count) + } + } + + self.frames.append(FrameMetadata(duration: duration)) + } catch { + self.isFailed = true + throw WriteError.generic } } @@ -607,23 +606,16 @@ private final class AnimationCacheItemAccessor { } final class CurrentFrame { - enum FrameType { - case key - case delta - } - let index: Int - let type: FrameType var remainingDuration: Double let duration: Double - let dctCoefficients: DctCoefficientsYUVA420 + let yuva: ImageYUVA420 - init(index: Int, type: FrameType, duration: Double, dctCoefficients: DctCoefficientsYUVA420) { + init(index: Int, duration: Double, yuva: ImageYUVA420) { self.index = index - self.type = type self.duration = duration self.remainingDuration = duration - self.dctCoefficients = dctCoefficients + self.yuva = yuva } } @@ -642,6 +634,7 @@ private final class AnimationCacheItemAccessor { private var currentFrame: CurrentFrame? private var currentYUVASurface: ImageYUVA420? + private var currentCoefficients: DctCoefficientsYUVA420? private let currentDctData: DctData private var sharedDctCoefficients: DctCoefficientsYUVA420? private var deltaCoefficients: DctCoefficientsYUVA420? @@ -696,7 +689,7 @@ private final class AnimationCacheItemAccessor { let frameType = Int(try compressedDataReader.readUInt32()) let dctCoefficients: DctCoefficientsYUVA420 - if let sharedDctCoefficients = self.sharedDctCoefficients, sharedDctCoefficients.yPlane.width == self.width, sharedDctCoefficients.yPlane.height == self.height { + if let sharedDctCoefficients = self.sharedDctCoefficients, sharedDctCoefficients.yPlane.width == self.width, sharedDctCoefficients.yPlane.height == self.height, !"".isEmpty { dctCoefficients = sharedDctCoefficients } else { dctCoefficients = DctCoefficientsYUVA420(width: self.width, height: self.height) @@ -738,7 +731,48 @@ private final class AnimationCacheItemAccessor { frameOffset += plane.data.count } - self.currentFrame = CurrentFrame(index: index, type: frameType == 1 ? .key : .delta, duration: self.durationMapping[index], dctCoefficients: dctCoefficients) + let yuvaSurface: ImageYUVA420 + if let currentYUVASurface = self.currentYUVASurface { + yuvaSurface = currentYUVASurface + } else { + yuvaSurface = ImageYUVA420(width: dctCoefficients.yPlane.width, height: dctCoefficients.yPlane.height, rowAlignment: nil) + } + + let currentCoefficients: DctCoefficientsYUVA420 + if let current = self.currentCoefficients { + currentCoefficients = current + } else { + currentCoefficients = DctCoefficientsYUVA420(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height) + self.currentCoefficients = currentCoefficients + } + + let deltaCoefficients: DctCoefficientsYUVA420 + if let current = self.deltaCoefficients { + deltaCoefficients = current + } else { + deltaCoefficients = DctCoefficientsYUVA420(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height) + self.deltaCoefficients = deltaCoefficients + } + + switch frameType { + case 1: + dctCoefficients.idct8x8(dctData: self.currentDctData, target: yuvaSurface) + yuvaSurface.toCoefficients(target: currentCoefficients) + default: + dctCoefficients.idct4x4(dctData: self.currentDctData, target: deltaCoefficients) + currentCoefficients.add(other: deltaCoefficients) + + if !"".isEmpty { + let deltaFloatCoefficients = FloatCoefficientsYUVA420(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height) + deltaCoefficients.toFloatCoefficients(target: deltaFloatCoefficients) + deltaFloatCoefficients.add(constant: 128.0) + deltaFloatCoefficients.toYUVA420(target: yuvaSurface) + } else { + currentCoefficients.toYUVA420(target: yuvaSurface) + } + } + + self.currentFrame = CurrentFrame(index: index, duration: self.durationMapping[index], yuva: yuvaSurface) } catch { self.currentFrame = nil self.compressedDataReader = nil @@ -773,75 +807,38 @@ private final class AnimationCacheItemAccessor { return nil } - let yuvaSurface: ImageYUVA420 switch requestedFormat { case .rgba: - if let currentYUVASurface = self.currentYUVASurface { - yuvaSurface = currentYUVASurface - } else { - yuvaSurface = ImageYUVA420(width: currentFrame.dctCoefficients.yPlane.width, height: currentFrame.dctCoefficients.yPlane.height, rowAlignment: nil) - } - case let .yuva(preferredRowAlignment): - yuvaSurface = ImageYUVA420(width: currentFrame.dctCoefficients.yPlane.width, height: currentFrame.dctCoefficients.yPlane.height, rowAlignment: preferredRowAlignment) - } - - switch currentFrame.type { - case .key: - currentFrame.dctCoefficients.idct(dctData: self.currentDctData, target: yuvaSurface) - case .delta: - let deltaCoefficients: DctCoefficientsYUVA420 - if let current = self.deltaCoefficients { - deltaCoefficients = current - } else { - deltaCoefficients = DctCoefficientsYUVA420(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height) - self.deltaCoefficients = deltaCoefficients - } - - currentFrame.dctCoefficients.idct(dctData: self.currentDctData, target: deltaCoefficients) - - if !"".isEmpty { - let deltaFloatCoefficients = FloatCoefficientsYUVA420(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height) - deltaCoefficients.toFloatCoefficients(target: deltaFloatCoefficients) - deltaFloatCoefficients.add(constant: 128.0) - deltaFloatCoefficients.toYUVA420(target: yuvaSurface) - } else { - yuvaSurface.subtract(other: deltaCoefficients) - } - } - - switch requestedFormat { - case .rgba: - let currentSurface = ImageARGB(width: yuvaSurface.yPlane.width, height: yuvaSurface.yPlane.height, rowAlignment: 32) - yuvaSurface.toARGB(target: currentSurface) - self.currentYUVASurface = yuvaSurface + let currentSurface = ImageARGB(width: currentFrame.yuva.yPlane.width, height: currentFrame.yuva.yPlane.height, rowAlignment: 32) + currentFrame.yuva.toARGB(target: currentSurface) return AnimationCacheItemFrame(format: .rgba(data: currentSurface.argbPlane.data, width: currentSurface.argbPlane.width, height: currentSurface.argbPlane.height, bytesPerRow: currentSurface.argbPlane.bytesPerRow), duration: currentFrame.duration) case .yuva: return AnimationCacheItemFrame( format: .yuva( y: AnimationCacheItemFrame.Plane( - data: yuvaSurface.yPlane.data, - width: yuvaSurface.yPlane.width, - height: yuvaSurface.yPlane.height, - bytesPerRow: yuvaSurface.yPlane.bytesPerRow + data: currentFrame.yuva.yPlane.data, + width: currentFrame.yuva.yPlane.width, + height: currentFrame.yuva.yPlane.height, + bytesPerRow: currentFrame.yuva.yPlane.bytesPerRow ), u: AnimationCacheItemFrame.Plane( - data: yuvaSurface.uPlane.data, - width: yuvaSurface.uPlane.width, - height: yuvaSurface.uPlane.height, - bytesPerRow: yuvaSurface.uPlane.bytesPerRow + data: currentFrame.yuva.uPlane.data, + width: currentFrame.yuva.uPlane.width, + height: currentFrame.yuva.uPlane.height, + bytesPerRow: currentFrame.yuva.uPlane.bytesPerRow ), v: AnimationCacheItemFrame.Plane( - data: yuvaSurface.vPlane.data, - width: yuvaSurface.vPlane.width, - height: yuvaSurface.vPlane.height, - bytesPerRow: yuvaSurface.vPlane.bytesPerRow + data: currentFrame.yuva.vPlane.data, + width: currentFrame.yuva.vPlane.width, + height: currentFrame.yuva.vPlane.height, + bytesPerRow: currentFrame.yuva.vPlane.bytesPerRow ), a: AnimationCacheItemFrame.Plane( - data: yuvaSurface.aPlane.data, - width: yuvaSurface.aPlane.width, - height: yuvaSurface.aPlane.height, - bytesPerRow: yuvaSurface.aPlane.bytesPerRow + data: currentFrame.yuva.aPlane.data, + width: currentFrame.yuva.aPlane.width, + height: currentFrame.yuva.aPlane.height, + bytesPerRow: currentFrame.yuva.aPlane.bytesPerRow ) ), duration: currentFrame.duration @@ -1125,7 +1122,7 @@ private func loadItem(path: String) throws -> AnimationCacheItem { } let formatVersion = readUInt32(data: compressedData, offset: offset) offset += 4 - if formatVersion != 5 { + if formatVersion != 6 { throw LoadItemError.dataError } diff --git a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift index 6ba90246d9..965e2f57b3 100644 --- a/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift +++ b/submodules/TelegramUI/Components/AnimationCache/Sources/ImageData.swift @@ -66,6 +66,14 @@ extension ImagePlane { } } } + + func add(other: DctCoefficientPlane) { + self.data.withUnsafeMutableBytes { bytes in + other.data.withUnsafeBytes { otherBytes in + addArraysUInt8Int16(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), otherBytes.baseAddress!.assumingMemoryBound(to: Int16.self), bytes.baseAddress!.assumingMemoryBound(to: Int8.self), Int32(bytes.count)) + } + } + } } final class ImageARGB { @@ -126,7 +134,15 @@ final class DctCoefficientPlane: CustomStringConvertible { func subtract(other: DctCoefficientPlane) { self.data.withUnsafeMutableBytes { bytes in other.data.withUnsafeBytes { otherBytes in - subtractArraysInt16(otherBytes.baseAddress!.assumingMemoryBound(to: Int16.self), bytes.baseAddress!.assumingMemoryBound(to: Int16.self), bytes.baseAddress!.assumingMemoryBound(to: Int16.self), Int32(bytes.count / 2)) + subtractArraysInt16(bytes.baseAddress!.assumingMemoryBound(to: Int16.self), otherBytes.baseAddress!.assumingMemoryBound(to: Int16.self), bytes.baseAddress!.assumingMemoryBound(to: Int16.self), Int32(bytes.count / 2)) + } + } + } + + func add(other: DctCoefficientPlane) { + self.data.withUnsafeMutableBytes { bytes in + other.data.withUnsafeBytes { otherBytes in + addArraysInt16(bytes.baseAddress!.assumingMemoryBound(to: Int16.self), otherBytes.baseAddress!.assumingMemoryBound(to: Int16.self), bytes.baseAddress!.assumingMemoryBound(to: Int16.self), Int32(bytes.count / 2)) } } } @@ -140,6 +156,14 @@ extension DctCoefficientPlane { } } } + + func toUInt8(target: ImagePlane) { + self.data.withUnsafeBytes { bytes in + target.data.withUnsafeMutableBytes { otherBytes in + convertInt16toUInt8(bytes.baseAddress!.assumingMemoryBound(to: UInt16.self), otherBytes.baseAddress!.assumingMemoryBound(to: UInt8.self), Int32(bytes.count / 2)) + } + } + } } final class DctCoefficientsYUVA420 { @@ -469,7 +493,42 @@ extension ImageYUVA420 { } } - func dct(dctData: DctData, target: DctCoefficientsYUVA420) { + func toCoefficients(target: DctCoefficientsYUVA420) { + precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height) + + for i in 0 ..< 4 { + let sourcePlane: ImagePlane + let targetPlane: DctCoefficientPlane + switch i { + case 0: + sourcePlane = self.yPlane + targetPlane = target.yPlane + case 1: + sourcePlane = self.uPlane + targetPlane = target.uPlane + case 2: + sourcePlane = self.vPlane + targetPlane = target.vPlane + case 3: + sourcePlane = self.aPlane + targetPlane = target.aPlane + default: + preconditionFailure() + } + + sourcePlane.data.withUnsafeBytes { sourceBytes in + let sourcePixels = sourceBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) + + targetPlane.data.withUnsafeMutableBytes { bytes in + let coefficients = bytes.baseAddress!.assumingMemoryBound(to: Int16.self) + + convertUInt8toInt16(sourcePixels, coefficients, Int32(sourceBytes.count)) + } + } + } + } + + func dct8x8(dctData: DctData, target: DctCoefficientsYUVA420) { precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height) for i in 0 ..< 4 { @@ -510,22 +569,23 @@ extension ImageYUVA420 { } } - func dct(dctData: DctData) -> DctCoefficientsYUVA420 { - let results = DctCoefficientsYUVA420(width: self.yPlane.width, height: self.yPlane.height) - self.dct(dctData: dctData, target: results) - return results - } - func subtract(other: DctCoefficientsYUVA420) { self.yPlane.subtract(other: other.yPlane) self.uPlane.subtract(other: other.uPlane) self.vPlane.subtract(other: other.vPlane) self.aPlane.subtract(other: other.aPlane) } + + func add(other: DctCoefficientsYUVA420) { + self.yPlane.add(other: other.yPlane) + self.uPlane.add(other: other.uPlane) + self.vPlane.add(other: other.vPlane) + self.aPlane.add(other: other.aPlane) + } } extension DctCoefficientsYUVA420 { - func idct(dctData: DctData, target: ImageYUVA420) { + func idct8x8(dctData: DctData, target: ImageYUVA420) { precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height) for i in 0 ..< 4 { @@ -566,13 +626,7 @@ extension DctCoefficientsYUVA420 { } } - func idct(dctData: DctData, rowAlignment: Int?) -> ImageYUVA420 { - let resultImage = ImageYUVA420(width: self.yPlane.width, height: self.yPlane.height, rowAlignment: rowAlignment) - self.idct(dctData: dctData, target: resultImage) - return resultImage - } - - func dct(dctData: DctData, target: DctCoefficientsYUVA420) { + func dct4x4(dctData: DctData, target: DctCoefficientsYUVA420) { precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height) for i in 0 ..< 4 { @@ -609,7 +663,7 @@ extension DctCoefficientsYUVA420 { } } - func idct(dctData: DctData, target: DctCoefficientsYUVA420) { + func idct4x4(dctData: DctData, target: DctCoefficientsYUVA420) { precondition(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height) for i in 0 ..< 4 { @@ -653,10 +707,26 @@ extension DctCoefficientsYUVA420 { self.aPlane.subtract(other: other.aPlane) } + func add(other: DctCoefficientsYUVA420) { + self.yPlane.add(other: other.yPlane) + self.uPlane.add(other: other.uPlane) + self.vPlane.add(other: other.vPlane) + self.aPlane.add(other: other.aPlane) + } + func toFloatCoefficients(target: FloatCoefficientsYUVA420) { self.yPlane.toFloatCoefficients(target: target.yPlane) self.uPlane.toFloatCoefficients(target: target.uPlane) self.vPlane.toFloatCoefficients(target: target.vPlane) self.aPlane.toFloatCoefficients(target: target.aPlane) } + + func toYUVA420(target: ImageYUVA420) { + assert(self.yPlane.width == target.yPlane.width && self.yPlane.height == target.yPlane.height) + + self.yPlane.toUInt8(target: target.yPlane) + self.uPlane.toUInt8(target: target.uPlane) + self.vPlane.toUInt8(target: target.vPlane) + self.aPlane.toUInt8(target: target.aPlane) + } } diff --git a/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift b/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift index a22701c090..7bb2c4e0d6 100644 --- a/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift +++ b/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift @@ -14,7 +14,6 @@ public func cacheLottieAnimation(data: Data, width: Int, height: Int, keyframeOn return } - let frameSkip: Int if animation.frameRate >= 60 { if ProcessInfo.processInfo.processorCount > 2 { diff --git a/submodules/TelegramUI/Components/VideoAnimationCache/Sources/VideoAnimationCache.swift b/submodules/TelegramUI/Components/VideoAnimationCache/Sources/VideoAnimationCache.swift index 8a6d53c1d9..b1543de3b6 100644 --- a/submodules/TelegramUI/Components/VideoAnimationCache/Sources/VideoAnimationCache.swift +++ b/submodules/TelegramUI/Components/VideoAnimationCache/Sources/VideoAnimationCache.swift @@ -45,7 +45,7 @@ public func cacheVideoAnimation(path: String, width: Int, height: Int, writer: A } } return frameDuration - }, proposedWidth: frame.width, proposedHeight: frame.height, insertKeyframe: true) + }, proposedWidth: frame.width, proposedHeight: frame.height, insertKeyframe: false) if firstFrameOnly { break