229 lines
6.4 KiB
Swift
229 lines
6.4 KiB
Swift
//
|
|
// Layer.swift
|
|
// lottie-swift
|
|
//
|
|
// Created by Brandon Withrow on 1/7/19.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// MARK: - LayerType + ClassFamily
|
|
|
|
/// Used for mapping a heterogeneous list to classes for parsing.
|
|
extension LayerType: ClassFamily {
|
|
static var discriminator: Discriminator = .type
|
|
|
|
func getType() -> AnyObject.Type {
|
|
switch self {
|
|
case .precomp:
|
|
return PreCompLayerModel.self
|
|
case .solid:
|
|
return SolidLayerModel.self
|
|
case .image:
|
|
return ImageLayerModel.self
|
|
case .null:
|
|
return LayerModel.self
|
|
case .shape:
|
|
return ShapeLayerModel.self
|
|
case .text:
|
|
return TextLayerModel.self
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - LayerType
|
|
|
|
public enum LayerType: Int, Codable {
|
|
case precomp
|
|
case solid
|
|
case image
|
|
case null
|
|
case shape
|
|
case text
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
self = try LayerType(rawValue: decoder.singleValueContainer().decode(RawValue.self)) ?? .null
|
|
}
|
|
}
|
|
|
|
// MARK: - MatteType
|
|
|
|
public enum MatteType: Int, Codable {
|
|
case none
|
|
case add
|
|
case invert
|
|
case unknown
|
|
}
|
|
|
|
// MARK: - BlendMode
|
|
|
|
public enum BlendMode: Int, Codable {
|
|
case normal
|
|
case multiply
|
|
case screen
|
|
case overlay
|
|
case darken
|
|
case lighten
|
|
case colorDodge
|
|
case colorBurn
|
|
case hardLight
|
|
case softLight
|
|
case difference
|
|
case exclusion
|
|
case hue
|
|
case saturation
|
|
case color
|
|
case luminosity
|
|
}
|
|
|
|
// MARK: - LayerModel
|
|
|
|
/// A base top container for shapes, images, and other view objects.
|
|
class LayerModel: Codable, DictionaryInitializable {
|
|
|
|
// MARK: Lifecycle
|
|
|
|
required init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: LayerModel.CodingKeys.self)
|
|
name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer"
|
|
index = try container.decodeIfPresent(Int.self, forKey: .index) ?? .random(in: Int.min...Int.max)
|
|
type = try container.decode(LayerType.self, forKey: .type)
|
|
coordinateSpace = try container.decodeIfPresent(CoordinateSpace.self, forKey: .coordinateSpace) ?? .type2d
|
|
inFrame = try container.decode(Double.self, forKey: .inFrame)
|
|
outFrame = try container.decode(Double.self, forKey: .outFrame)
|
|
startTime = try container.decode(Double.self, forKey: .startTime)
|
|
transform = try container.decode(Transform.self, forKey: .transform)
|
|
parent = try container.decodeIfPresent(Int.self, forKey: .parent)
|
|
blendMode = try container.decodeIfPresent(BlendMode.self, forKey: .blendMode) ?? .normal
|
|
masks = try container.decodeIfPresent([Mask].self, forKey: .masks)
|
|
timeStretch = try container.decodeIfPresent(Double.self, forKey: .timeStretch) ?? 1
|
|
matte = try container.decodeIfPresent(MatteType.self, forKey: .matte)
|
|
hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden) ?? false
|
|
}
|
|
|
|
required init(dictionary: [String: Any]) throws {
|
|
name = (try? dictionary.value(for: CodingKeys.name)) ?? "Layer"
|
|
index = try dictionary.value(for: CodingKeys.index) ?? .random(in: Int.min...Int.max)
|
|
type = LayerType(rawValue: try dictionary.value(for: CodingKeys.type)) ?? .null
|
|
if
|
|
let coordinateSpaceRawValue = dictionary[CodingKeys.coordinateSpace.rawValue] as? Int,
|
|
let coordinateSpace = CoordinateSpace(rawValue: coordinateSpaceRawValue)
|
|
{
|
|
self.coordinateSpace = coordinateSpace
|
|
} else {
|
|
coordinateSpace = .type2d
|
|
}
|
|
inFrame = try dictionary.value(for: CodingKeys.inFrame)
|
|
outFrame = try dictionary.value(for: CodingKeys.outFrame)
|
|
startTime = try dictionary.value(for: CodingKeys.startTime)
|
|
transform = try Transform(dictionary: try dictionary.value(for: CodingKeys.transform))
|
|
parent = try? dictionary.value(for: CodingKeys.parent)
|
|
if
|
|
let blendModeRawValue = dictionary[CodingKeys.blendMode.rawValue] as? Int,
|
|
let blendMode = BlendMode(rawValue: blendModeRawValue)
|
|
{
|
|
self.blendMode = blendMode
|
|
} else {
|
|
blendMode = .normal
|
|
}
|
|
if let maskDictionaries = dictionary[CodingKeys.masks.rawValue] as? [[String: Any]] {
|
|
masks = try maskDictionaries.map({ try Mask(dictionary: $0) })
|
|
} else {
|
|
masks = nil
|
|
}
|
|
timeStretch = (try? dictionary.value(for: CodingKeys.timeStretch)) ?? 1
|
|
if let matteRawValue = dictionary[CodingKeys.matte.rawValue] as? Int {
|
|
matte = MatteType(rawValue: matteRawValue)
|
|
} else {
|
|
matte = nil
|
|
}
|
|
hidden = (try? dictionary.value(for: CodingKeys.hidden)) ?? false
|
|
}
|
|
|
|
// MARK: Internal
|
|
|
|
/// The readable name of the layer
|
|
let name: String
|
|
|
|
/// The index of the layer
|
|
let index: Int
|
|
|
|
/// The type of the layer.
|
|
let type: LayerType
|
|
|
|
/// The coordinate space
|
|
let coordinateSpace: CoordinateSpace
|
|
|
|
/// The in time of the layer in frames.
|
|
let inFrame: Double
|
|
/// The out time of the layer in frames.
|
|
let outFrame: Double
|
|
|
|
/// The start time of the layer in frames.
|
|
let startTime: Double
|
|
|
|
/// The transform of the layer
|
|
let transform: Transform
|
|
|
|
/// The index of the parent layer, if applicable.
|
|
let parent: Int?
|
|
|
|
/// The blending mode for the layer
|
|
let blendMode: BlendMode
|
|
|
|
/// An array of masks for the layer.
|
|
let masks: [Mask]?
|
|
|
|
/// A number that stretches time by a multiplier
|
|
let timeStretch: Double
|
|
|
|
/// The type of matte if any.
|
|
let matte: MatteType?
|
|
|
|
let hidden: Bool
|
|
|
|
// MARK: Fileprivate
|
|
|
|
fileprivate enum CodingKeys: String, CodingKey {
|
|
case name = "nm"
|
|
case index = "ind"
|
|
case type = "ty"
|
|
case coordinateSpace = "ddd"
|
|
case inFrame = "ip"
|
|
case outFrame = "op"
|
|
case startTime = "st"
|
|
case transform = "ks"
|
|
case parent
|
|
case blendMode = "bm"
|
|
case masks = "masksProperties"
|
|
case timeStretch = "sr"
|
|
case matte = "tt"
|
|
case hidden = "hd"
|
|
}
|
|
}
|
|
|
|
extension Array where Element == LayerModel {
|
|
|
|
static func fromDictionaries(_ dictionaries: [[String: Any]]) throws -> [LayerModel] {
|
|
try dictionaries.compactMap { dictionary in
|
|
let layerType = dictionary[LayerModel.CodingKeys.type.rawValue] as? Int
|
|
switch LayerType(rawValue: layerType ?? LayerType.null.rawValue) {
|
|
case .precomp:
|
|
return try PreCompLayerModel(dictionary: dictionary)
|
|
case .solid:
|
|
return try SolidLayerModel(dictionary: dictionary)
|
|
case .image:
|
|
return try ImageLayerModel(dictionary: dictionary)
|
|
case .null:
|
|
return try LayerModel(dictionary: dictionary)
|
|
case .shape:
|
|
return try ShapeLayerModel(dictionary: dictionary)
|
|
case .text:
|
|
return try TextLayerModel(dictionary: dictionary)
|
|
case .none:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|