mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Lottie refactoring
This commit is contained in:
@@ -1,519 +1,3 @@
|
||||
import Foundation
|
||||
import LottieCpp
|
||||
|
||||
final class WriteBuffer {
|
||||
private(set) var data: Data
|
||||
private var capacity: Int
|
||||
var length: Int
|
||||
|
||||
init() {
|
||||
self.capacity = 1024
|
||||
self.data = Data(count: self.capacity)
|
||||
self.length = 0
|
||||
}
|
||||
|
||||
func trim() {
|
||||
self.data.count = self.length
|
||||
self.capacity = self.data.count
|
||||
}
|
||||
|
||||
func write(bytes: UnsafeRawBufferPointer) {
|
||||
if self.data.count < self.length + bytes.count {
|
||||
self.data.count = self.data.count * 2
|
||||
}
|
||||
self.data.withUnsafeMutableBytes { buffer -> Void in
|
||||
memcpy(buffer.baseAddress!.advanced(by: self.length), bytes.baseAddress!, bytes.count)
|
||||
}
|
||||
self.length += bytes.count
|
||||
}
|
||||
|
||||
func write(uInt32 value: UInt32) {
|
||||
var value = value
|
||||
withUnsafeBytes(of: &value, { bytes in
|
||||
self.write(bytes: bytes)
|
||||
})
|
||||
}
|
||||
|
||||
func write(uInt16 value: UInt16) {
|
||||
var value = value
|
||||
withUnsafeBytes(of: &value, { bytes in
|
||||
self.write(bytes: bytes)
|
||||
})
|
||||
}
|
||||
|
||||
func write(uInt8 value: UInt8) {
|
||||
var value = value
|
||||
withUnsafeBytes(of: &value, { bytes in
|
||||
self.write(bytes: bytes)
|
||||
})
|
||||
}
|
||||
|
||||
func write(float value: Float) {
|
||||
var value = value
|
||||
withUnsafeBytes(of: &value, { bytes in
|
||||
self.write(bytes: bytes)
|
||||
})
|
||||
}
|
||||
|
||||
func write(point: CGPoint) {
|
||||
self.write(float: Float(point.x))
|
||||
self.write(float: Float(point.y))
|
||||
}
|
||||
|
||||
func write(size: CGSize) {
|
||||
self.write(float: Float(size.width))
|
||||
self.write(float: Float(size.height))
|
||||
}
|
||||
|
||||
func write(rect: CGRect) {
|
||||
self.write(point: rect.origin)
|
||||
self.write(size: rect.size)
|
||||
}
|
||||
|
||||
func write(transform: CATransform3D) {
|
||||
self.write(float: Float(transform.m11))
|
||||
self.write(float: Float(transform.m12))
|
||||
self.write(float: Float(transform.m13))
|
||||
self.write(float: Float(transform.m14))
|
||||
self.write(float: Float(transform.m21))
|
||||
self.write(float: Float(transform.m22))
|
||||
self.write(float: Float(transform.m23))
|
||||
self.write(float: Float(transform.m24))
|
||||
self.write(float: Float(transform.m31))
|
||||
self.write(float: Float(transform.m32))
|
||||
self.write(float: Float(transform.m33))
|
||||
self.write(float: Float(transform.m34))
|
||||
self.write(float: Float(transform.m41))
|
||||
self.write(float: Float(transform.m42))
|
||||
self.write(float: Float(transform.m43))
|
||||
self.write(float: Float(transform.m44))
|
||||
}
|
||||
}
|
||||
|
||||
final class ReadBuffer {
|
||||
private let data: Data
|
||||
private var offset: Int
|
||||
|
||||
init(data: Data) {
|
||||
self.data = data
|
||||
self.offset = 0
|
||||
}
|
||||
|
||||
func read(bytes: UnsafeMutableRawBufferPointer) {
|
||||
if self.offset + bytes.count <= self.data.count {
|
||||
self.data.withUnsafeBytes { buffer -> Void in
|
||||
memcpy(bytes.baseAddress!, buffer.baseAddress!.advanced(by: self.offset), bytes.count)
|
||||
}
|
||||
self.offset += bytes.count
|
||||
} else {
|
||||
preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
func readUInt32() -> UInt32 {
|
||||
var value: UInt32 = 0
|
||||
withUnsafeMutableBytes(of: &value, { bytes in
|
||||
self.read(bytes: bytes)
|
||||
})
|
||||
return value
|
||||
}
|
||||
|
||||
func readUInt16() -> UInt16 {
|
||||
var value: UInt16 = 0
|
||||
withUnsafeMutableBytes(of: &value, { bytes in
|
||||
self.read(bytes: bytes)
|
||||
})
|
||||
return value
|
||||
}
|
||||
|
||||
func readUInt8() -> UInt8 {
|
||||
var value: UInt8 = 0
|
||||
withUnsafeMutableBytes(of: &value, { bytes in
|
||||
self.read(bytes: bytes)
|
||||
})
|
||||
return value
|
||||
}
|
||||
|
||||
func readFloat() -> Float {
|
||||
var value: Float = 0
|
||||
withUnsafeMutableBytes(of: &value, { bytes in
|
||||
self.read(bytes: bytes)
|
||||
})
|
||||
return value
|
||||
}
|
||||
|
||||
func readPoint() -> CGPoint {
|
||||
return CGPoint(x: CGFloat(self.readFloat()), y: CGFloat(self.readFloat()))
|
||||
}
|
||||
|
||||
func readSize() -> CGSize {
|
||||
return CGSize(width: CGFloat(self.readFloat()), height: CGFloat(self.readFloat()))
|
||||
}
|
||||
|
||||
func readRect() -> CGRect {
|
||||
return CGRect(origin: self.readPoint(), size: self.readSize())
|
||||
}
|
||||
|
||||
func readTransform() -> CATransform3D {
|
||||
return CATransform3D(
|
||||
m11: CGFloat(self.readFloat()),
|
||||
m12: CGFloat(self.readFloat()),
|
||||
m13: CGFloat(self.readFloat()),
|
||||
m14: CGFloat(self.readFloat()),
|
||||
m21: CGFloat(self.readFloat()),
|
||||
m22: CGFloat(self.readFloat()),
|
||||
m23: CGFloat(self.readFloat()),
|
||||
m24: CGFloat(self.readFloat()),
|
||||
m31: CGFloat(self.readFloat()),
|
||||
m32: CGFloat(self.readFloat()),
|
||||
m33: CGFloat(self.readFloat()),
|
||||
m34: CGFloat(self.readFloat()),
|
||||
m41: CGFloat(self.readFloat()),
|
||||
m42: CGFloat(self.readFloat()),
|
||||
m43: CGFloat(self.readFloat()),
|
||||
m44: CGFloat(self.readFloat())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension LottieColor {
|
||||
init(argb: UInt32) {
|
||||
self.init(r: CGFloat((argb >> 16) & 0xff) / 255.0, g: CGFloat((argb >> 8) & 0xff) / 255.0, b: CGFloat(argb & 0xff) / 255.0, a: CGFloat((argb >> 24) & 0xff) / 255.0)
|
||||
}
|
||||
|
||||
var argb: UInt32 {
|
||||
return (UInt32(self.a * 255.0) << 24) | (UInt32(max(0.0, self.r) * 255.0) << 16) | (UInt32(max(0.0, self.g) * 255.0) << 8) | (UInt32(max(0.0, self.b) * 255.0))
|
||||
}
|
||||
}
|
||||
|
||||
private struct NodeFlags: OptionSet {
|
||||
var rawValue: UInt8
|
||||
|
||||
init(rawValue: UInt8) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let masksToBounds = NodeFlags(rawValue: 1 << 0)
|
||||
static let isHidden = NodeFlags(rawValue: 1 << 1)
|
||||
static let hasSimpleContents = NodeFlags(rawValue: 1 << 2)
|
||||
static let isInvertedMatte = NodeFlags(rawValue: 1 << 3)
|
||||
|
||||
static let hasRenderContent = NodeFlags(rawValue: 1 << 4)
|
||||
static let hasSubnodes = NodeFlags(rawValue: 1 << 5)
|
||||
static let hasMask = NodeFlags(rawValue: 1 << 6)
|
||||
}
|
||||
|
||||
private struct LottieContentFlags: OptionSet {
|
||||
var rawValue: UInt8
|
||||
|
||||
init(rawValue: UInt8) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let hasStroke = LottieContentFlags(rawValue: 1 << 0)
|
||||
static let hasFill = LottieContentFlags(rawValue: 1 << 1)
|
||||
}
|
||||
|
||||
func serializePath(buffer: WriteBuffer, path: LottiePath) {
|
||||
let lengthOffset = buffer.length
|
||||
buffer.write(uInt32: 0)
|
||||
|
||||
path.enumerateItems { pathItem in
|
||||
switch pathItem.pointee.type {
|
||||
case .moveTo:
|
||||
let point = pathItem.pointee.points.0
|
||||
buffer.write(uInt8: 0)
|
||||
buffer.write(point: point)
|
||||
case .lineTo:
|
||||
let point = pathItem.pointee.points.0
|
||||
buffer.write(uInt8: 1)
|
||||
buffer.write(point: point)
|
||||
case .curveTo:
|
||||
let cp1 = pathItem.pointee.points.0
|
||||
let cp2 = pathItem.pointee.points.1
|
||||
let point = pathItem.pointee.points.2
|
||||
|
||||
buffer.write(uInt8: 2)
|
||||
buffer.write(point: cp1)
|
||||
buffer.write(point: cp2)
|
||||
buffer.write(point: point)
|
||||
case .close:
|
||||
buffer.write(uInt8: 3)
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let dataLength = buffer.length - lengthOffset - 4
|
||||
|
||||
let previousLength = buffer.length
|
||||
buffer.length = lengthOffset
|
||||
buffer.write(uInt32: UInt32(dataLength))
|
||||
buffer.length = previousLength
|
||||
}
|
||||
|
||||
func deserializePath(buffer: ReadBuffer) -> LottiePath {
|
||||
let itemDataLength = Int(buffer.readUInt32())
|
||||
var itemData = Data(count: itemDataLength)
|
||||
itemData.withUnsafeMutableBytes { bytes in
|
||||
buffer.read(bytes: bytes)
|
||||
}
|
||||
|
||||
return LottiePath(customData: itemData)
|
||||
}
|
||||
|
||||
func serializeContentShading(buffer: WriteBuffer, shading: LottieRenderContentShading) {
|
||||
if let shading = shading as? LottieRenderContentSolidShading {
|
||||
buffer.write(uInt8: 0)
|
||||
buffer.write(uInt32: shading.color.argb)
|
||||
buffer.write(uInt8: UInt8(clamping: Int(shading.opacity * 255.0)))
|
||||
} else if let shading = shading as? LottieRenderContentGradientShading {
|
||||
buffer.write(uInt8: 1)
|
||||
buffer.write(uInt8: UInt8(clamping: Int(shading.opacity * 255.0)))
|
||||
buffer.write(uInt8: UInt8(shading.gradientType.rawValue))
|
||||
let colorStopCount = min(shading.colorStops.count, 255)
|
||||
buffer.write(uInt8: UInt8(colorStopCount))
|
||||
for i in 0 ..< colorStopCount {
|
||||
buffer.write(uInt32: shading.colorStops[i].color.argb)
|
||||
buffer.write(float: Float(shading.colorStops[i].location))
|
||||
}
|
||||
buffer.write(point: shading.start)
|
||||
buffer.write(point: shading.end)
|
||||
} else {
|
||||
buffer.write(uInt8: 0)
|
||||
buffer.write(uInt8: UInt8(clamping: Int(1.0 * 255.0)))
|
||||
}
|
||||
}
|
||||
|
||||
func deserializeContentShading(buffer: ReadBuffer) -> LottieRenderContentShading {
|
||||
switch buffer.readUInt8() {
|
||||
case 0:
|
||||
return LottieRenderContentSolidShading(
|
||||
color: LottieColor(argb: buffer.readUInt32()),
|
||||
opacity: CGFloat(buffer.readUInt8()) / 255.0
|
||||
)
|
||||
case 1:
|
||||
let opacity = CGFloat(buffer.readUInt8()) / 255.0
|
||||
let gradientType = LottieGradientType(rawValue: UInt(buffer.readUInt8()))!
|
||||
|
||||
var colorStops: [LottieColorStop] = []
|
||||
let colorStopCount = Int(buffer.readUInt8())
|
||||
for _ in 0 ..< colorStopCount {
|
||||
colorStops.append(LottieColorStop(
|
||||
color: LottieColor(argb: buffer.readUInt32()),
|
||||
location: CGFloat(buffer.readFloat())
|
||||
))
|
||||
}
|
||||
|
||||
let start = buffer.readPoint()
|
||||
let end = buffer.readPoint()
|
||||
|
||||
return LottieRenderContentGradientShading(
|
||||
opacity: opacity,
|
||||
gradientType: gradientType,
|
||||
colorStops: colorStops,
|
||||
start: start,
|
||||
end: end
|
||||
)
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
func serializeStroke(buffer: WriteBuffer, stroke: LottieRenderContentStroke) {
|
||||
serializeContentShading(buffer: buffer, shading: stroke.shading)
|
||||
buffer.write(float: Float(stroke.lineWidth))
|
||||
buffer.write(uInt8: UInt8(stroke.lineJoin.rawValue))
|
||||
buffer.write(uInt8: UInt8(stroke.lineCap.rawValue))
|
||||
buffer.write(float: Float(stroke.miterLimit))
|
||||
}
|
||||
|
||||
func deserializeStroke(buffer: ReadBuffer) -> LottieRenderContentStroke {
|
||||
return LottieRenderContentStroke(
|
||||
shading: deserializeContentShading(buffer: buffer),
|
||||
lineWidth: CGFloat(buffer.readFloat()),
|
||||
lineJoin: CGLineJoin(rawValue: Int32(buffer.readUInt8()))!,
|
||||
lineCap: CGLineCap(rawValue: Int32(buffer.readUInt8()))!,
|
||||
miterLimit: CGFloat(buffer.readFloat()),
|
||||
dashPhase: 0.0,
|
||||
dashPattern: nil
|
||||
)
|
||||
}
|
||||
|
||||
func serializeFill(buffer: WriteBuffer, fill: LottieRenderContentFill) {
|
||||
serializeContentShading(buffer: buffer, shading: fill.shading)
|
||||
buffer.write(uInt8: UInt8(fill.fillRule.rawValue))
|
||||
}
|
||||
|
||||
func deserializeFill(buffer: ReadBuffer) -> LottieRenderContentFill {
|
||||
return LottieRenderContentFill(
|
||||
shading: deserializeContentShading(buffer: buffer),
|
||||
fillRule: LottieFillRule(rawValue: UInt(buffer.readUInt8()))!
|
||||
)
|
||||
}
|
||||
|
||||
func serializeRenderContent(buffer: WriteBuffer, renderContent: LottieRenderContent) {
|
||||
var flags: LottieContentFlags = []
|
||||
if renderContent.stroke != nil {
|
||||
flags.insert(.hasStroke)
|
||||
}
|
||||
if renderContent.fill != nil {
|
||||
flags.insert(.hasFill)
|
||||
}
|
||||
buffer.write(uInt8: flags.rawValue)
|
||||
|
||||
serializePath(buffer: buffer, path: renderContent.path)
|
||||
if let stroke = renderContent.stroke {
|
||||
serializeStroke(buffer: buffer, stroke: stroke)
|
||||
}
|
||||
if let fill = renderContent.fill {
|
||||
serializeFill(buffer: buffer, fill: fill)
|
||||
}
|
||||
}
|
||||
|
||||
func deserializeRenderContent(buffer: ReadBuffer) -> LottieRenderContent {
|
||||
let flags = LottieContentFlags(rawValue: buffer.readUInt8())
|
||||
|
||||
let path = deserializePath(buffer: buffer)
|
||||
|
||||
var stroke: LottieRenderContentStroke?
|
||||
if flags.contains(.hasStroke) {
|
||||
stroke = deserializeStroke(buffer: buffer)
|
||||
}
|
||||
|
||||
var fill: LottieRenderContentFill?
|
||||
if flags.contains(.hasFill) {
|
||||
fill = deserializeFill(buffer: buffer)
|
||||
}
|
||||
|
||||
return LottieRenderContent(
|
||||
path: path,
|
||||
stroke: stroke,
|
||||
fill: fill
|
||||
)
|
||||
}
|
||||
|
||||
func serializeNode(buffer: WriteBuffer, node: LottieRenderNode) {
|
||||
var flags: NodeFlags = []
|
||||
if node.masksToBounds {
|
||||
flags.insert(.masksToBounds)
|
||||
}
|
||||
if node.isHidden {
|
||||
flags.insert(.isHidden)
|
||||
}
|
||||
if node.hasSimpleContents {
|
||||
flags.insert(.hasSimpleContents)
|
||||
}
|
||||
if node.isInvertedMatte {
|
||||
flags.insert(.isInvertedMatte)
|
||||
}
|
||||
if node.renderContent != nil {
|
||||
flags.insert(.hasRenderContent)
|
||||
}
|
||||
if !node.subnodes.isEmpty {
|
||||
flags.insert(.hasSubnodes)
|
||||
}
|
||||
if node.mask != nil {
|
||||
flags.insert(.hasMask)
|
||||
}
|
||||
|
||||
buffer.write(uInt8: flags.rawValue)
|
||||
|
||||
buffer.write(point: node.position)
|
||||
buffer.write(rect: node.bounds)
|
||||
buffer.write(transform: node.transform)
|
||||
buffer.write(uInt8: UInt8(clamping: Int(node.opacity * 255.0)))
|
||||
buffer.write(rect: node.globalRect)
|
||||
buffer.write(transform: node.globalTransform)
|
||||
|
||||
if let renderContent = node.renderContent {
|
||||
serializeRenderContent(buffer: buffer, renderContent: renderContent)
|
||||
}
|
||||
if !node.subnodes.isEmpty {
|
||||
let count = min(node.subnodes.count, 4095)
|
||||
buffer.write(uInt16: UInt16(count))
|
||||
for i in 0 ..< count {
|
||||
serializeNode(buffer: buffer, node: node.subnodes[i])
|
||||
}
|
||||
}
|
||||
if let mask = node.mask {
|
||||
serializeNode(buffer: buffer, node: mask)
|
||||
}
|
||||
}
|
||||
|
||||
func deserializeNode(buffer: ReadBuffer) -> LottieRenderNode {
|
||||
let flags = NodeFlags(rawValue: buffer.readUInt8())
|
||||
|
||||
let position = buffer.readPoint()
|
||||
let bounds = buffer.readRect()
|
||||
let transform = buffer.readTransform()
|
||||
let opacity = CGFloat(buffer.readUInt8()) / 255.0
|
||||
let globalRect = buffer.readRect()
|
||||
let globalTransform = buffer.readTransform()
|
||||
|
||||
var renderContent: LottieRenderContent?
|
||||
if flags.contains(.hasRenderContent) {
|
||||
renderContent = deserializeRenderContent(buffer: buffer)
|
||||
}
|
||||
var subnodes: [LottieRenderNode] = []
|
||||
if flags.contains(.hasSubnodes) {
|
||||
let count = Int(buffer.readUInt16())
|
||||
for _ in 0 ..< count {
|
||||
subnodes.append(deserializeNode(buffer: buffer))
|
||||
}
|
||||
}
|
||||
var mask: LottieRenderNode?
|
||||
if flags.contains(.hasMask) {
|
||||
mask = deserializeNode(buffer: buffer)
|
||||
}
|
||||
|
||||
return LottieRenderNode(
|
||||
position: position,
|
||||
bounds: bounds,
|
||||
transform: transform,
|
||||
opacity: opacity,
|
||||
masksToBounds: flags.contains(.masksToBounds),
|
||||
isHidden: flags.contains(.isHidden),
|
||||
globalRect: globalRect,
|
||||
globalTransform: globalTransform,
|
||||
renderContent: renderContent,
|
||||
hasSimpleContents: flags.contains(.hasSimpleContents),
|
||||
isInvertedMatte: flags.contains(.isInvertedMatte),
|
||||
subnodes: subnodes,
|
||||
mask: mask
|
||||
)
|
||||
}
|
||||
|
||||
public struct SerializedLottieMetalFrameMapping {
|
||||
var size: CGSize = CGSize()
|
||||
var frameCount: Int = 0
|
||||
var framesPerSecond: Int = 0
|
||||
var frameRanges: [Int: Range<Int>] = [:]
|
||||
}
|
||||
|
||||
func serializeFrameMapping(buffer: WriteBuffer, frameMapping: SerializedLottieMetalFrameMapping) {
|
||||
buffer.write(size: frameMapping.size)
|
||||
buffer.write(uInt32: UInt32(frameMapping.frameCount))
|
||||
buffer.write(uInt32: UInt32(frameMapping.framesPerSecond))
|
||||
for (frame, range) in frameMapping.frameRanges.sorted(by: { $0.key < $1.key }) {
|
||||
buffer.write(uInt32: UInt32(frame))
|
||||
buffer.write(uInt32: UInt32(range.lowerBound))
|
||||
buffer.write(uInt32: UInt32(range.upperBound))
|
||||
}
|
||||
}
|
||||
|
||||
func deserializeFrameMapping(buffer: ReadBuffer) -> SerializedLottieMetalFrameMapping {
|
||||
var frameMapping = SerializedLottieMetalFrameMapping()
|
||||
|
||||
frameMapping.size = buffer.readSize()
|
||||
frameMapping.frameCount = Int(buffer.readUInt32())
|
||||
frameMapping.framesPerSecond = Int(buffer.readUInt32())
|
||||
for _ in 0 ..< frameMapping.frameCount {
|
||||
let frame = Int(buffer.readUInt32())
|
||||
let lowerBound = Int(buffer.readUInt32())
|
||||
let upperBound = Int(buffer.readUInt32())
|
||||
frameMapping.frameRanges[frame] = lowerBound ..< upperBound
|
||||
}
|
||||
|
||||
return frameMapping
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user