Fix animation cache

This commit is contained in:
Ali 2022-07-30 23:30:14 +02:00
parent adb9767edd
commit 47239681a7
10 changed files with 506 additions and 348 deletions

View File

@ -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",
],
)

Binary file not shown.

View File

@ -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
}

View File

@ -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__
}

View File

@ -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];
}
}
}*/
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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