Skip to content

Commit

Permalink
Support encoding images with frame source
Browse files Browse the repository at this point in the history
  • Loading branch information
yeatse committed Jan 13, 2024
1 parent 9546dbb commit 5d3cd56
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 9 deletions.
29 changes: 22 additions & 7 deletions Sources/Image+WebP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import Foundation
import KingfisherWebP_ObjC
#endif

#if canImport(AppKit)
import AppKit
#endif

// MARK: - Image Representation
extension KingfisherWrapper where Base: KFCrossPlatformImage {
/// isLossy (0=lossy , 1=lossless (default)).
Expand All @@ -37,16 +41,27 @@ extension KingfisherWrapper where Base: KFCrossPlatformImage {
/// isLossy (0=lossy , 1=lossless (default)).
/// Note that the default values are isLossy= false and quality=75.0f
private func animatedWebPRepresentation(isLossy: Bool = false, quality: Float = 75.0) -> Data? {
#if os(macOS)
return nil
#else
guard let images = base.images?.compactMap({ $0.cgImage }) else {
let imageInfo: [CFString: Any]
if let frameSource = frameSource {
let frameCount = frameSource.frameCount
imageInfo = [
kWebPAnimatedImageFrames: (0..<frameCount).map({ frameSource.frame(at: $0) }),
kWebPAnimatedImageFrameDurations: (0..<frameCount).map({ frameSource.duration(at: $0) }),
]
} else {
#if os(macOS)
return nil
#else
guard let images = base.images?.compactMap({ $0.cgImage }) else {
return nil
}
imageInfo = [
kWebPAnimatedImageFrames: images,
kWebPAnimatedImageDuration: base.duration
]
#endif
}
let imageInfo = [ kWebPAnimatedImageFrames: images,
kWebPAnimatedImageDuration: NSNumber(value: base.duration) ] as [CFString : Any]
return WebPDataCreateWithAnimatedImageInfo(imageInfo as CFDictionary, isLossy, quality) as Data?
#endif
}
}

Expand Down
59 changes: 57 additions & 2 deletions Tests/KingfisherWebPTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ class KingfisherWebPTests: XCTestCase {

#if os(macOS)
func testMultipleFrameEncoding() {
let s = WebPSerializer.default
var s = WebPSerializer.default
s.originalDataUsed = false

animationFileNames.forEach { fileName in
let originalData = Data(fileName: fileName)
Expand All @@ -149,7 +150,8 @@ class KingfisherWebPTests: XCTestCase {
}
#else
func testMultipleFrameEncoding() {
let s = WebPSerializer.default
var s = WebPSerializer.default
s.originalDataUsed = false

animationFileNames.forEach { fileName in
let originalData = Data(fileName: fileName)
Expand All @@ -171,6 +173,59 @@ class KingfisherWebPTests: XCTestCase {
}
#endif

func testVariableFrameEncoding() {
var s = WebPSerializer.default
s.originalDataUsed = false

animationFileNames.forEach { fileName in
let originalData = Data(fileName: fileName)
let originalImage = KingfisherWrapper.animatedImage(data: originalData, options: .init())!

let webpData = s.data(with: originalImage, original: nil)
XCTAssertNotNil(webpData, fileName)

let imageFromWebPData = s.image(with: webpData!, options: .init(nil))
XCTAssertNotNil(imageFromWebPData, fileName)

let originalFrameSource = originalImage.kf.frameSource!
let encodedFrameSource = imageFromWebPData!.kf.frameSource!
XCTAssertEqual(originalFrameSource.frameCount, encodedFrameSource.frameCount)

(0..<originalFrameSource.frameCount).forEach { index in
let frame1 = KFCrossPlatformImage(cgImage: originalFrameSource.frame(at: index)!, size: .zero)
let frame2 = KFCrossPlatformImage(cgImage: encodedFrameSource.frame(at: index)!, size: .zero)
XCTAssertTrue(frame1.renderEqual(to: frame2), "Frame \(index) of \(fileName) should be equal")

let duration1 = originalFrameSource.duration(at: index)
let duration2 = encodedFrameSource.duration(at: index)
XCTAssertEqual(duration1, duration2, "Duration in frame \(index) of \(fileName) should be equal")
}
}
}

func testOriginalDataIsUsed() {
let s = WebPSerializer.default
XCTAssertTrue(s.originalDataUsed)

let randomData = Data((0..<10).map { _ in UInt8.random(in: 0...255) })
let encoded = s.data(with: KFCrossPlatformImage(), original: randomData)
XCTAssertEqual(encoded, randomData, "Original data should be used")

struct RandomFrameSource: ImageFrameSource {
let data: Data? = Data((0..<10).map { _ in UInt8.random(in: 0...255) })
let frameCount: Int = 10
func duration(at index: Int) -> TimeInterval { return 0 }
func frame(at index: Int, maxSize: CGSize?) -> CGImage? {
KFCrossPlatformImage(data: .init(fileName: "cover.png"))?.kfCGImage
}
}

let source = RandomFrameSource()
let image = KingfisherWrapper.animatedImage(source: source, options: .init())!
let encoded2 = s.data(with: image, original: nil)
XCTAssertEqual(encoded2, source.data, "Original data should be used")
}

func testEncodingPerformance() {
let s = WebPSerializer.default
let images = fileNames.compactMap { fileName -> KFCrossPlatformImage? in
Expand Down

0 comments on commit 5d3cd56

Please sign in to comment.