Skip to content

Commit

Permalink
Fix the imageProvider in frame extracting
Browse files Browse the repository at this point in the history
  • Loading branch information
dreampiggy committed Jul 8, 2022
1 parent 4a4cd8a commit 2dc5856
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 19 deletions.
29 changes: 26 additions & 3 deletions Example/Tests/Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,39 @@ class Tests: XCTestCase {
self.waitForExpectations(timeout: 5, handler: nil)
}

func testLottieImageWithBundle() throws {
let bundle = Bundle(for: type(of: self))
let fileURL = bundle.url(forResource: "Assets", withExtension: "json")!
let lottieData = try Data(contentsOf: fileURL)
let animation = try JSONDecoder().decode(Animation.self, from: lottieData)
let animationView = AnimationView(animation: animation, imageProvider: BundleImageProvider(bundle: bundle, searchPath: nil))
let renderer = SDGraphicsImageRenderer(size: animation.size)
let viewImage = renderer.image { context in
animationView.drawHierarchy(in: CGRect(origin: CGPoint(x: 0, y: 0), size: animation.size), afterScreenUpdates: true)
}
// Pick the color to check
let color1 = try XCTUnwrap(viewImage.sd_color(at: CGPoint(x: 150, y: 150)))
XCTAssertEqual(color1.toHexString(), "#00d1c1");

let lottieImage = LottieImage(animation: animation)
lottieImage.imageProvider = BundleImageProvider(bundle: bundle, searchPath: nil)
let posterFrame = try XCTUnwrap(lottieImage.animatedImageFrame(at: 0))
// Pick the color to check
let color2 = try XCTUnwrap(posterFrame.sd_color(at: CGPoint(x: 150, y: 150)))
XCTAssertEqual(color2.toHexString(), "#00d1c1");
}


func testLottieImageExtractFrame() {
let exception = self.expectation(description: "LottieImage extract frame")
let lottieUrl = URL(string: "https://raw.githubusercontent.com/airbnb/lottie-web/master/demo/gatin/data.json")!
let task = URLSession.shared.dataTask(with: lottieUrl) { data, _, _ in
if let data = data, let animation = try? JSONDecoder().decode(Animation.self, from: data) {
let lottieImage = LottieImage(animation: animation)
let frameCount = lottieImage?.animatedImageFrameCount ?? 0
let frameCount = lottieImage.animatedImageFrameCount
XCTAssertEqual(frameCount, 80)
let posterFrame = lottieImage?.animatedImageFrame(at: 0)
let lastFrame = lottieImage?.animatedImageFrame(at: frameCount - 1)
let posterFrame = lottieImage.animatedImageFrame(at: 0)
let lastFrame = lottieImage.animatedImageFrame(at: frameCount - 1)
XCTAssertNotNil(posterFrame)
XCTAssertNotNil(lastFrame)
XCTAssertNotEqual(posterFrame, lastFrame)
Expand Down
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ github "SDWebImage/SDWebImageLottiePlugin"

Lottie 3.4 version's new `Lottie.RenderingEngine = .coreAnimation` solve the huge performance regression in the issue [here](https://github.com/airbnb/lottie-ios/issues/895) 🚀

So from SDWebImageLottiePlugin v1.0.0, we drop the Lottie 2 support, as well as the Objective-C support because Lottie 3 use the pure Swift.
So from SDWebImageLottiePlugin v1.0.0, we drop the Lottie 2 support, as well as the Objective-C support because Lottie 3 use pure Swift. And therefore, we drop the iOS 9-10 support because the upstream dependency need iOS 11+.

For user who still use Lottie 2 and Objective-C, please check the 0.x version updated to [0.3.0](https://github.com/SDWebImage/SDWebImageLottiePlugin/releases/tag/0.3.0)

Expand All @@ -59,9 +59,25 @@ animationView.sd_setImage(with: lottieJSONURL)
```

Note:
+ You can also load lottie json files on `LOTAnimatedControl`, like switch button.
+ You can also load lottie json files on `AnimatedControl`, like switch button.
+ Lottie animation does not start automatically, you can use the completion block, or UITableView/UICollectionView's will display timing to play.
+ If your Lottie json files contains references to App bundle images, you can use `SDWebImageContextLottieBundle` context option to pass the NSBundle object to load it.

```swift
animationView.sd_setImage(with: lottieUrl, completed: { _,_,_,_ in
self.animationView.play(fromProgress: 0, toProgress: 1, loopMode: .repeat(5)) { finished in
// ...
}
}
```


+ If your Lottie json files contains references to App bundle images, just set the `imageProvider` before the lottie animation start.

```swift
let bundle = Bundle(for: MyBundleClass.self)
animationView.imageProvider = BundleImageProvider(bundle: bundle, searchPath: nil)
animationView.sd_setImage(with: lottieUrl)
```

### Advanced usage

Expand All @@ -72,6 +88,8 @@ This Lottie plugin use a wrapper class `LottieImage` because of SDWebImage's [cu
```swift
let animation = try? JSONDecoder().decode(Animation.self, from: data)
let animatedImage = LottieImage(animation: animation)
// Optional, custom image bundle
LottieImage.imageProvider = BundleImageProvider(bundle: bundle, searchPath: nil)
// Snapshot Lottie animation frame
UIImage *posterFrame = animatedImage.animatedImageFrame(at: 0)
TimeInterval duration = animatedImage.animatedImageDuration(at: 0)
Expand Down
55 changes: 45 additions & 10 deletions SDWebImageLottiePlugin/Classes/LottieCompositionLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,64 @@
import SDWebImage
@testable import Lottie


/// This layer is used if as Lottie 2's`LOTCompositionContainer`.
/// This layer is used like Lottie 2's`LOTCompositionContainer`. Typically private, use with caution :)
/// Which can render the specify frame into CGBitmapContext, used for debugging or some snapshot use case
class LottieCompositionLayer : CALayer {
var animationLayers: [CompositionLayer]?
init(animation: Animation) {
let layerImageProvider = LayerImageProvider(imageProvider: BundleImageProvider(bundle: .main, searchPath: nil).cachedImageProvider, assets: animation.assetLibrary?.imageAssets)
let layers = animation.layers.initializeCompositionLayers(assetLibrary: animation.assetLibrary, layerImageProvider: layerImageProvider, textProvider: DefaultTextProvider(), fontProvider: DefaultFontProvider(), frameRate: CGFloat(animation.framerate))
public class LottieCompositionLayer : CALayer {

/// All composition sublayers, in hierarchy (reverse order) unlike `sublayers`
private var animationLayers: [CompositionLayer] = []

/// Initializes an LottieCompositionLayer with an animation.
/// - Parameters:
/// - animation: animation
/// - imageProvider: imageProvider
/// - textProvider: textProvider
/// - fontProvider: fontProvider
public init(animation: Animation,
imageProvider: AnimationImageProvider? = nil,
textProvider: AnimationTextProvider? = nil,
fontProvider: AnimationFontProvider? = nil) {
// Steal from MainThreadAnimationLayer.swift
let layerImageProvider = LayerImageProvider(imageProvider: (imageProvider ?? BundleImageProvider(bundle: .main, searchPath: nil)).cachedImageProvider, assets: animation.assetLibrary?.imageAssets)
let layerTextProvider = LayerTextProvider(textProvider: textProvider ?? DefaultTextProvider())
let layerFontProvider = LayerFontProvider(fontProvider: fontProvider ?? DefaultFontProvider())

let layers = animation.layers.initializeCompositionLayers(assetLibrary: animation.assetLibrary, layerImageProvider: layerImageProvider, textProvider: layerTextProvider.textProvider, fontProvider: layerFontProvider.fontProvider, frameRate: CGFloat(animation.framerate))
super.init()
bounds = animation.bounds

var imageLayers = [ImageCompositionLayer]()
var textLayers = [TextCompositionLayer]()

for layer in layers.reversed() {
layer.bounds = bounds
animationLayers.append(layer)
if let imageLayer = layer as? ImageCompositionLayer {
imageLayers.append(imageLayer)
}
if let textLayer = layer as? TextCompositionLayer {
textLayers.append(textLayer)
}
addSublayer(layer)
}
animationLayers = layers

layerImageProvider.addImageLayers(imageLayers)
layerImageProvider.reloadImages()
layerTextProvider.addTextLayers(textLayers)
layerTextProvider.reloadTexts()
layerFontProvider.addTextLayers(textLayers)
layerFontProvider.reloadTexts()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func displayWithFrame(frame: CGFloat, forceUpdates: Bool) {
animationLayers?.forEach { $0.displayWithFrame(frame: frame, forceUpdates: forceUpdates) }
/// Display the layer tree to specify frame time
/// - Parameters:
/// - frame: The AnimationFrameTime(Seconds * Framerate)
/// - forceUpdates: Whether to force update the tree node, true need time but more accurate rendering result
public func displayWithFrame(frame: CGFloat, forceUpdates: Bool) {
animationLayers.forEach { $0.displayWithFrame(frame: frame, forceUpdates: forceUpdates) }
}
}
15 changes: 12 additions & 3 deletions SDWebImageLottiePlugin/Classes/LottieImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,18 @@ public class LottieImage : PlatformImage, SDAnimatedImageProtocol {
/// The lottie animation model
public var animation: Animation?

/// The lottie image provider, used for frame extracting
public var imageProvider: AnimationImageProvider?

/// The lottie text provider, used for frame extracting
public var textProvider: AnimationTextProvider?

/// The lottie font provider, used for frame extracting
public var fontProvider: AnimationFontProvider?

/// Init the LottieImage with lottie animation model
/// - Parameter animation: animation
public required init?(animation: Animation) {
public required init(animation: Animation) {
#if os(iOS) || os(tvOS)
super.init()
#else
Expand Down Expand Up @@ -127,7 +136,7 @@ public class LottieImage : PlatformImage, SDAnimatedImageProtocol {
guard let animation = animation else {
return nil
}
let compositionLayer = LottieCompositionLayer(animation: animation)
let compositionLayer = LottieCompositionLayer(animation: animation, imageProvider: imageProvider, textProvider: textProvider, fontProvider: fontProvider)
return compositionLayer
}()

Expand All @@ -146,7 +155,7 @@ public class LottieImage : PlatformImage, SDAnimatedImageProtocol {
return image
}

// MARK: - SDAnimatedImageProvider
// MARK: - SDAnimatedImageProvider && Frame Extracting
public private(set) var animatedImageData: Data?

public var animatedImageFrameCount: UInt {
Expand Down

0 comments on commit 2dc5856

Please sign in to comment.