Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,21 @@
import Foundation
import UIKit
@objc(Application)
public final class Application: UIApplication {
}
@objc(AppDelegate)
public final class AppDelegate: NSObject, UIApplicationDelegate {
public var window: UIWindow?
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let window = UIWindow()
self.window = window
window.rootViewController = ViewController()
window.makeKeyAndVisible()
return true
}
}
@@ -0,0 +1,354 @@
import Foundation
import UIKit
import LottieMetal
import LottieCpp
import RLottieBinding
import Display
import Accelerate
import QOILoader
import SoftwareLottieRenderer
import LottieSwift
@available(iOS 13.0, *)
func areImagesEqual(_ lhs: UIImage, _ rhs: UIImage, allowedDifference: Double) -> (UIImage?, UIImage) {
let lhsBuffer = try! vImage_Buffer(cgImage: lhs.cgImage!)
let rhsBuffer = try! vImage_Buffer(cgImage: rhs.cgImage!)
let deltaBuffer = try! vImage_Buffer(cgImage: lhs.cgImage!)
defer {
lhsBuffer.free()
rhsBuffer.free()
deltaBuffer.free()
}
memset(deltaBuffer.data, 0, Int(deltaBuffer.height) * deltaBuffer.rowBytes)
let maxDifferenceCount = Int((Double(Int(lhs.size.width) * Int(lhs.size.height)) * allowedDifference))
var foundDifferenceCount = 0
outer: for y in 0 ..< Int(lhs.size.height) {
let lhsRowPixels = lhsBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes)
let rhsRowPixels = rhsBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes)
let deltaRowPixels = deltaBuffer.data.assumingMemoryBound(to: UInt8.self).advanced(by: y * lhsBuffer.rowBytes)
for x in 0 ..< Int(lhs.size.width) {
let lhs0 = lhsRowPixels.advanced(by: x * 4 + 0).pointee
let lhs1 = lhsRowPixels.advanced(by: x * 4 + 1).pointee
let lhs2 = lhsRowPixels.advanced(by: x * 4 + 2).pointee
let lhs3 = lhsRowPixels.advanced(by: x * 4 + 3).pointee
let rhs0 = rhsRowPixels.advanced(by: x * 4 + 0).pointee
let rhs1 = rhsRowPixels.advanced(by: x * 4 + 1).pointee
let rhs2 = rhsRowPixels.advanced(by: x * 4 + 2).pointee
let rhs3 = rhsRowPixels.advanced(by: x * 4 + 3).pointee
let maxDiff = 25
if abs(Int(lhs0) - Int(rhs0)) > maxDiff || abs(Int(lhs1) - Int(rhs1)) > maxDiff || abs(Int(lhs2) - Int(rhs2)) > maxDiff || abs(Int(lhs3) - Int(rhs3)) > maxDiff {
deltaRowPixels.advanced(by: x * 4 + 0).pointee = 255
deltaRowPixels.advanced(by: x * 4 + 1).pointee = 0
deltaRowPixels.advanced(by: x * 4 + 2).pointee = 0
deltaRowPixels.advanced(by: x * 4 + 3).pointee = 255
foundDifferenceCount += 1
}
}
}
let colorSpace = Unmanaged<CGColorSpace>.passRetained(lhs.cgImage!.colorSpace!)
let deltaImage = try! deltaBuffer.createCGImage(format: vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: colorSpace, bitmapInfo: lhs.cgImage!.bitmapInfo, version: 0, decode: nil, renderingIntent: .defaultIntent), flags: .doNotTile)
if foundDifferenceCount > maxDifferenceCount {
let diffImage = try! lhsBuffer.createCGImage(format: vImage_CGImageFormat(bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: colorSpace, bitmapInfo: lhs.cgImage!.bitmapInfo, version: 0, decode: nil, renderingIntent: .defaultIntent), flags: .doNotTile)
return (UIImage(cgImage: diffImage), UIImage(cgImage: deltaImage))
} else {
return (nil, UIImage(cgImage: deltaImage))
}
}
@available(iOS 13.0, *)
func processDrawAnimation(baseCachePath: String, path: String, name: String, size: CGSize, allowedDifference: Double, alwaysDraw: Bool, useNonReferenceRendering: Bool, updateImage: @escaping (UIImage?, UIImage?, UIImage?) -> Void) async -> Bool {
guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
print("Could not load \(path)")
return false
}
let cacheFolderPath = cacheReferenceFolderPath(baseCachePath: baseCachePath, width: Int(size.width), name: name)
if !FileManager.default.fileExists(atPath: cacheFolderPath) {
let _ = await cacheReferenceAnimation(baseCachePath: baseCachePath, width: Int(size.width), path: path, name: name)
}
guard let renderer = SoftwareLottieRenderer(data: data) else {
print("Could not parse animation at \(path)")
return false
}
for i in 0 ..< min(100000, renderer.frameCount) {
let frameResult = autoreleasepool {
let frameIndex = i % renderer.frameCount
let referenceImageData = try! Data(contentsOf: URL(fileURLWithPath: cacheFolderPath + "/frame\(frameIndex)"))
let referenceImage = decompressImageFrame(data: referenceImageData)
renderer.setFrame(CGFloat(frameIndex))
let image = renderer.render(for: size, useReferenceRendering: !useNonReferenceRendering, canUseMoreMemory: false, skipImageGeneration: false)!
let (diffImage, deltaImage) = areImagesEqual(image, referenceImage, allowedDifference: allowedDifference)
if !useNonReferenceRendering, let diffImage {
updateImage(diffImage, referenceImage, deltaImage)
print("Mismatch in frame \(frameIndex)")
return false
} else {
if alwaysDraw {
updateImage(image, referenceImage, diffImage)
}
return true
}
}
/*if #available(iOS 16.0, *) {
try? await Task.sleep(for: .seconds(0.1))
}*/
if !frameResult {
return false
}
}
return true
}
private func processAnimationFolder(basePath: String, path: String, stopOnFailure: Bool, process: (String, String) -> Bool) -> Bool {
let directoryPath = "\(basePath)\(path.isEmpty ? "" : "/")/\(path)"
for fileName in try! FileManager.default.contentsOfDirectory(atPath: directoryPath) {
let filePath = directoryPath + "/" + fileName
var isDirectory = ObjCBool(false)
if FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory) {
if isDirectory.boolValue {
if !processAnimationFolder(basePath: basePath, path: "\(path)\(path.isEmpty ? "" : "/")/\(fileName)", stopOnFailure: stopOnFailure, process: process) {
if stopOnFailure {
return false
}
}
} else if fileName.hasSuffix("json") {
var processAnimationResult = false
autoreleasepool {
processAnimationResult = process(filePath, fileName)
}
if !processAnimationResult {
print("Error processing \(path)\(path.isEmpty ? "" : "/")\(fileName)")
if stopOnFailure {
return false
}
} else {
print("[OK] processing \(path)\(path.isEmpty ? "" : "/")\(fileName)")
}
}
}
}
return true
}
func buildAnimationFolderItems(basePath: String, path: String) -> [(String, String)] {
var result: [(String, String)] = []
let directoryPath = "\(basePath)\(path.isEmpty ? "" : "/")/\(path)"
for fileName in try! FileManager.default.contentsOfDirectory(atPath: directoryPath) {
let filePath = directoryPath + "/" + fileName
var isDirectory = ObjCBool(false)
if FileManager.default.fileExists(atPath: filePath, isDirectory: &isDirectory) {
if isDirectory.boolValue {
result.append(contentsOf: buildAnimationFolderItems(basePath: basePath, path: "\(path)\(path.isEmpty ? "" : "/")/\(fileName)"))
} else if fileName.hasSuffix("json") {
result.append((filePath, fileName))
}
}
}
return result
}
@available (iOS 13.0, *)
private func processAnimationFolderItems(items: [(String, String)], countPerBucket: Int, stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool {
let bucketCount = items.count / countPerBucket
var buckets: [[(String, String)]] = []
for item in items {
if buckets.isEmpty {
buckets.append([])
}
if buckets[buckets.count - 1].count < bucketCount {
buckets[buckets.count - 1].append(item)
} else {
buckets.append([item])
}
}
var count = 0
for bucket in buckets {
for (filePath, fileName) in bucket {
var processAnimationResult = false
processAnimationResult = await process(filePath, fileName, true)
if !processAnimationResult {
print("Error processing \(fileName)")
if stopOnFailure {
return false
}
} else {
count += 1
print("[OK \(count) / \(items.count)] processing \(fileName)")
}
}
}
return true
}
@available(iOS 13.0, *)
private func processAnimationFolderItemsParallel(items: [(String, String)], stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool {
let bucketCount = items.count / 7
var buckets: [[(String, String)]] = []
for item in items {
if buckets.isEmpty {
buckets.append([])
}
if buckets[buckets.count - 1].count < bucketCount {
buckets[buckets.count - 1].append(item)
} else {
buckets.append([item])
}
}
class AtomicCounter {
var value: Int = 0
}
let count = AtomicCounter()
let itemCount = items.count
let countQueue = DispatchQueue(label: "com.example.count-queue")
let result = await withTaskGroup(of: Bool.self, body: { group in
var alwaysDraw = true
for bucket in buckets {
let alwaysDrawValue = alwaysDraw
alwaysDraw = false
group.addTask(operation: {
for (filePath, fileName) in bucket {
var processAnimationResult = false
processAnimationResult = await process(filePath, fileName, alwaysDrawValue)
if !processAnimationResult {
print("Error processing \(fileName)")
if stopOnFailure {
return false
}
} else {
countQueue.async {
count.value += 1
print("[OK \(count.value) / \(itemCount)] processing \(fileName)")
}
}
}
return true
})
}
for await result in group {
if !result {
return false
}
}
return true
})
return result
}
@available (iOS 13.0, *)
func processAnimationFolderAsync(basePath: String, path: String, stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool {
let items = buildAnimationFolderItems(basePath: basePath, path: path)
return await processAnimationFolderItems(items: items, countPerBucket: 1, stopOnFailure: stopOnFailure, process: process)
}
@available(iOS 13.0, *)
func processAnimationFolderParallel(basePath: String, path: String, stopOnFailure: Bool, process: @escaping (String, String, Bool) async -> Bool) async -> Bool {
let items = buildAnimationFolderItems(basePath: basePath, path: path)
return await processAnimationFolderItemsParallel(items: items, stopOnFailure: stopOnFailure, process: process)
}
func cacheReferenceFolderPath(baseCachePath: String, width: Int, name: String) -> String {
return baseCachePath + "/" + name + "_\(width)"
}
func compressImageFrame(image: UIImage) -> Data {
return encodeImageQOI(image)!
}
func decompressImageFrame(data: Data) -> UIImage {
return decodeImageQOI(data)!
}
final class ReferenceLottieAnimationItem {
private let referenceAnimation: Animation
private let referenceLayer: MainThreadAnimationLayer
let frameCount: Int
init?(path: String) {
guard let referenceAnimation = Animation.filepath(path) else {
return nil
}
self.referenceAnimation = referenceAnimation
self.referenceLayer = MainThreadAnimationLayer(animation: referenceAnimation, imageProvider: BlankImageProvider(), textProvider: DefaultTextProvider(), fontProvider: DefaultFontProvider())
self.referenceLayer.position = referenceAnimation.bounds.center
self.referenceLayer.isOpaque = false
self.referenceLayer.backgroundColor = nil
self.frameCount = Int(referenceAnimation.endFrame - referenceAnimation.startFrame)
}
func setFrame(index: Int) {
self.referenceLayer.currentFrame = self.referenceAnimation.startFrame + CGFloat(index)
self.referenceLayer.displayUpdate()
}
func makeImage(width: Int, height: Int) -> UIImage? {
let size = CGSize(width: CGFloat(width), height: CGFloat(width))
let referenceContext = ImageContext(width: width, height: height)
referenceContext.context.clear(CGRect(origin: CGPoint(), size: size))
referenceContext.context.scaleBy(x: size.width / CGFloat(self.referenceAnimation.width), y: size.height / CGFloat(self.referenceAnimation.height))
referenceLayer.render(in: referenceContext.context)
return referenceContext.makeImage()
}
}
@MainActor
func cacheReferenceAnimation(baseCachePath: String, width: Int, path: String, name: String) -> String {
let targetFolderPath = cacheReferenceFolderPath(baseCachePath: baseCachePath, width: width, name: name)
if FileManager.default.fileExists(atPath: targetFolderPath) {
return targetFolderPath
}
guard let referenceItem = ReferenceLottieAnimationItem(path: path) else {
preconditionFailure("Could not load reference animation at \(path)")
}
let cacheFolderPath = NSTemporaryDirectory() + "\(UInt64.random(in: 0 ... UInt64.max))"
let _ = try? FileManager.default.createDirectory(atPath: cacheFolderPath, withIntermediateDirectories: true)
for i in 0 ..< min(100000, referenceItem.frameCount) {
let frameIndex = i % referenceItem.frameCount
referenceItem.setFrame(index: frameIndex)
let referenceImage = referenceItem.makeImage(width: width, height: width)!
try! compressImageFrame(image: referenceImage).write(to: URL(fileURLWithPath: cacheFolderPath + "/frame\(i)"))
}
let _ = try! FileManager.default.moveItem(atPath: cacheFolderPath, toPath: targetFolderPath)
return targetFolderPath
}
@@ -0,0 +1,41 @@
import Foundation
import UIKit
private func alignUp(size: Int, align: Int) -> Int {
precondition(((align - 1) & align) == 0, "Align must be a power of two")
let alignmentMask = align - 1
return (size + alignmentMask) & ~alignmentMask
}
final class ImageContext {
let context: CGContext
init(width: Int, height: Int, isMask: Bool = false) {
let bytesPerRow: Int
let colorSpace: CGColorSpace
let bitmapInfo: CGBitmapInfo
if isMask {
bytesPerRow = alignUp(size: width, align: 16)
colorSpace = CGColorSpaceCreateDeviceGray()
bitmapInfo = CGBitmapInfo(rawValue: 0)
} else {
bytesPerRow = alignUp(size: width * 4, align: 16)
colorSpace = CGColorSpaceCreateDeviceRGB()
bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
}
self.context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)!
self.context.clear(CGRect(origin: CGPoint(), size: CGSize(width: width, height: height)))
//context.interpolationQuality = .none
//context.setShouldAntialias(false)
//context.setAllowsAntialiasing(false)
}
func makeImage() -> UIImage {
return UIImage(cgImage: context.makeImage()!)
}
}
@@ -0,0 +1,360 @@
import Foundation
import UIKit
import LottieMetal
import LottieCpp
import RLottieBinding
import MetalEngine
import Display
import LottieSwift
import SoftwareLottieRenderer
@available(iOS 13.0, *)
private final class ReferenceCompareTest {
private let view: UIView
private let imageView = UIImageView()
private let referenceImageView = UIImageView()
private let deltaImageView = UIImageView()
init(view: UIView, testNonReference: Bool) {
lottieSwift_getPathNativeBoundingBox = { path in
return getPathNativeBoundingBox(path)
}
self.view = view
self.view.backgroundColor = .white
let topInset: CGFloat = 50.0
self.view.addSubview(self.imageView)
self.imageView.layer.magnificationFilter = .nearest
self.imageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset), size: CGSize(width: 256.0, height: 256.0))
self.imageView.backgroundColor = self.view.backgroundColor
self.imageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
self.view.addSubview(self.referenceImageView)
self.referenceImageView.layer.magnificationFilter = .nearest
self.referenceImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0))
self.referenceImageView.backgroundColor = self.view.backgroundColor
self.referenceImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
self.view.addSubview(self.deltaImageView)
self.deltaImageView.layer.magnificationFilter = .nearest
self.deltaImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0 + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0))
self.deltaImageView.backgroundColor = self.view.backgroundColor
self.deltaImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
Task.detached {
let sizeMapping: [String: Int] = [
"5170488605398795246.json": 512,
"35707580709863506.json": 512,
"35707580709863507.json": 512,
"1258816259754246.json": 512,
"1258816259754248.json": 512,
"35707580709863489.json": 512,
"1258816259754150.json": 512,
"35707580709863494.json": 512,
"5021586753580958116.json": 512,
"35707580709863509.json": 512,
"5282957555314728059.json": 512,
"fireworks.json": 512,
"750766425144033565.json": 512,
"1258816259754276.json": 1024,
"1471004892762996753.json": 1024,
"4985886809322947159.json": 1024,
"35707580709863490.json": 1024,
"4986037051573928320.json": 512,
"1258816259754029.json": 1024,
"4987794066860147124.json": 1024,
"1258816259754212.json": 1024,
"750766425144033464.json": 1024,
"750766425144033567.json": 1024,
"1391391008142393350.json": 1024
]
let allowedDifferences: [String: Double] = [
"1258816259754165.json": 0.04
]
let defaultSize = 128
let baseCachePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).path + "/frame-cache"
let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: baseCachePath), withIntermediateDirectories: true, attributes: nil)
print("Frame cache: \(baseCachePath)")
for (filePath, fileName) in buildAnimationFolderItems(basePath: bundlePath, path: "") {
let _ = await cacheReferenceAnimation(baseCachePath: baseCachePath, width: sizeMapping[fileName] ?? defaultSize, path: filePath, name: fileName)
}
var continueFromName: String?
//continueFromName = "562563904580878375.json"
let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: !testNonReference, process: { path, name, alwaysDraw in
if let continueFromNameValue = continueFromName {
if continueFromNameValue == name {
continueFromName = nil
} else {
return true
}
}
let size = sizeMapping[name] ?? defaultSize
let result = await processDrawAnimation(baseCachePath: baseCachePath, path: path, name: name, size: CGSize(width: size, height: size), allowedDifference: allowedDifferences[name] ?? 0.01, alwaysDraw: alwaysDraw, useNonReferenceRendering: testNonReference, updateImage: { image, referenceImage, differenceImage in
DispatchQueue.main.async {
self.imageView.image = image
self.referenceImageView.image = referenceImage
self.deltaImageView.image = differenceImage
}
})
return result
})
}
}
}
@available(iOS 13.0, *)
private final class ManualReferenceCompareTest {
private final class Item {
let renderer: SoftwareLottieRenderer
let referenceRenderer: ReferenceLottieAnimationItem
init(renderer: SoftwareLottieRenderer, referenceRenderer: ReferenceLottieAnimationItem) {
self.renderer = renderer
self.referenceRenderer = referenceRenderer
}
}
private let view: UIView
private let imageView = UIImageView()
private let referenceImageView = UIImageView()
private let labelView = UILabel()
private let renderSize: CGSize
private let testNonReference: Bool
private let fileList: [(filePath: String, fileName: String)]
private var currentFileIndex: Int = 0
private var currentItem: Item?
private var frameDisplayLink: SharedDisplayLinkDriver.Link?
init(view: UIView) {
self.testNonReference = true
self.currentFileIndex = 0
lottieSwift_getPathNativeBoundingBox = { path in
return getPathNativeBoundingBox(path)
}
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
self.fileList = buildAnimationFolderItems(basePath: bundlePath, path: "")
if let index = self.fileList.firstIndex(where: { $0.fileName == "shit.json" }) {
self.currentFileIndex = index
}
self.renderSize = CGSize(width: 256.0, height: 256.0)
self.view = view
self.view.backgroundColor = .white
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
let topInset: CGFloat = 50.0
self.view.addSubview(self.imageView)
self.imageView.layer.magnificationFilter = .nearest
self.imageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset), size: CGSize(width: 256.0, height: 256.0))
self.imageView.backgroundColor = self.view.backgroundColor
self.imageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
self.view.addSubview(self.referenceImageView)
self.referenceImageView.layer.magnificationFilter = .nearest
self.referenceImageView.frame = CGRect(origin: CGPoint(x: 10.0, y: topInset + 256.0 + 1.0), size: CGSize(width: 256.0, height: 256.0))
self.referenceImageView.backgroundColor = self.view.backgroundColor
self.referenceImageView.transform = CGAffineTransform.init(scaleX: 1.0, y: -1.0)
self.view.addSubview(self.labelView)
self.updateCurrentAnimation()
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
if recognizer.location(in: self.view).x <= self.view.bounds.width * 0.5 {
if self.currentFileIndex != 0 {
self.currentFileIndex = self.currentFileIndex - 1
}
} else {
self.currentFileIndex = (self.currentFileIndex + 1) % self.fileList.count
}
self.updateCurrentAnimation()
}
}
private func updateCurrentAnimation() {
self.imageView.image = nil
self.referenceImageView.image = nil
self.currentItem = nil
self.labelView.text = "\(self.currentFileIndex + 1) / \(self.fileList.count)"
self.labelView.sizeToFit()
self.labelView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.height - 10.0 - self.labelView.bounds.height)
self.frameDisplayLink?.invalidate()
self.frameDisplayLink = nil
let (filePath, _) = self.fileList[self.currentFileIndex]
guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
print("Could not load \(filePath)")
return
}
guard let renderer = SoftwareLottieRenderer(data: data) else {
print("Could not load animation at \(filePath)")
return
}
guard let referenceRenderer = ReferenceLottieAnimationItem(path: filePath) else {
print("Could not load reference animation at \(filePath)")
return
}
let currentItem = Item(renderer: renderer, referenceRenderer: referenceRenderer)
self.currentItem = currentItem
var animationTime = 0.0
let secondsPerFrame = 1.0 / Double(renderer.framesPerSecond)
let frameDisplayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] deltaTime in
guard let self, let currentItem = self.currentItem else {
return
}
var frameIndex = animationTime / secondsPerFrame
frameIndex = frameIndex.truncatingRemainder(dividingBy: Double(currentItem.renderer.frameCount))
currentItem.renderer.setFrame(frameIndex)
let image = currentItem.renderer.render(for: self.renderSize, useReferenceRendering: !self.testNonReference, canUseMoreMemory: false, skipImageGeneration: false)!
self.imageView.image = image
currentItem.referenceRenderer.setFrame(index: Int(frameIndex))
let referenceImage = currentItem.referenceRenderer.makeImage(width: Int(self.renderSize.width), height: Int(self.renderSize.height))!
self.referenceImageView.image = referenceImage
animationTime += deltaTime
})
self.frameDisplayLink = frameDisplayLink
frameDisplayLink.isPaused = false
}
}
public final class ViewController: UIViewController {
private var link: SharedDisplayLinkDriver.Link?
private var test: AnyObject?
override public func viewDidLoad() {
super.viewDidLoad()
SharedDisplayLinkDriver.shared.updateForegroundState(true)
let bundlePath = Bundle.main.path(forResource: "TestDataBundle", ofType: "bundle")!
let filePath = bundlePath + "/fire.json"
let performanceFrameSize = 128
self.view.layer.addSublayer(MetalEngine.shared.rootLayer)
if !"".isEmpty {
if #available(iOS 13.0, *) {
self.test = ReferenceCompareTest(view: self.view, testNonReference: false)
}
} else if "".isEmpty {
if #available(iOS 13.0, *) {
self.test = ManualReferenceCompareTest(view: self.view)
}
} else if !"".isEmpty {
/*let cachedAnimation = cacheLottieMetalAnimation(path: filePath)!
let animation = parseCachedLottieMetalAnimation(data: cachedAnimation)!
/*let animationData = try! Data(contentsOf: URL(fileURLWithPath: filePath))
var startTime = CFAbsoluteTimeGetCurrent()
let animation = LottieAnimation(data: animationData)!
print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
startTime = CFAbsoluteTimeGetCurrent()
let animationContainer = LottieAnimationContainer(animation: animation)
animationContainer.update(0)
print("Build time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")*/
let lottieLayer = LottieContentLayer(content: animation)
lottieLayer.frame = CGRect(origin: CGPoint(x: 10.0, y: 50.0), size: CGSize(width: 256.0, height: 256.0))
self.view.layer.addSublayer(lottieLayer)
lottieLayer.setNeedsUpdate()
self.link = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { _ in
lottieLayer.frameIndex = (lottieLayer.frameIndex + 1) % animation.frameCount
lottieLayer.setNeedsUpdate()
})*/
} else if "".isEmpty {
Thread {
let animationData = try! Data(contentsOf: URL(fileURLWithPath: filePath))
var startTime = CFAbsoluteTimeGetCurrent()
let animationRenderer = SoftwareLottieRenderer(data: animationData)!
print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
startTime = CFAbsoluteTimeGetCurrent()
var numUpdates: Int = 0
var frameIndex = 0
while true {
animationRenderer.setFrame(CGFloat(frameIndex))
let _ = animationRenderer.render(for: CGSize(width: CGFloat(performanceFrameSize), height: CGFloat(performanceFrameSize)), useReferenceRendering: false, canUseMoreMemory: true, skipImageGeneration: true)
frameIndex = (frameIndex + 1) % animationRenderer.frameCount
numUpdates += 1
let timestamp = CFAbsoluteTimeGetCurrent()
let deltaTime = timestamp - startTime
if deltaTime > 2.0 {
let updatesPerSecond = Double(numUpdates) / deltaTime
startTime = timestamp
numUpdates = 0
print("Ours: updatesPerSecond: \(updatesPerSecond)")
}
}
}.start()
} else {
Thread {
var startTime = CFAbsoluteTimeGetCurrent()
let animationInstance = LottieInstance(data: try! Data(contentsOf: URL(fileURLWithPath: filePath)), fitzModifier: .none, colorReplacements: nil, cacheKey: "")!
print("Load time: \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms")
let frameBuffer = malloc(performanceFrameSize * 4 * performanceFrameSize)!
defer {
free(frameBuffer)
}
startTime = CFAbsoluteTimeGetCurrent()
var numUpdates: Int = 0
var frameIndex = 0
while true {
animationInstance.renderFrame(with: Int32(frameIndex), into: frameBuffer, width: Int32(performanceFrameSize), height: Int32(performanceFrameSize), bytesPerRow: Int32(performanceFrameSize * 4))
frameIndex = (frameIndex + 1) % Int(animationInstance.frameCount)
numUpdates += 1
let timestamp = CFAbsoluteTimeGetCurrent()
let deltaTime = timestamp - startTime
if deltaTime > 2.0 {
let updatesPerSecond = Double(numUpdates) / deltaTime
startTime = timestamp
numUpdates = 0
print("Rlottie: updatesPerSecond: \(updatesPerSecond)")
}
}
}.start()
}
}
}