瀏覽代碼

Add only first frame setting

onevcat 9 年之前
父節點
當前提交
b808515aca

+ 6 - 4
Sources/CacheSerializer.swift

@@ -77,9 +77,11 @@ public struct DefaultCacheSerializer: CacheSerializer {
     }
     }
     
     
     public func image(with data: Data, options: KingfisherOptionsInfo?) -> Image? {
     public func image(with data: Data, options: KingfisherOptionsInfo?) -> Image? {
-        let scale = (options ?? KingfisherEmptyOptionsInfo).scaleFactor
-        let preloadAllGIFData = (options ?? KingfisherEmptyOptionsInfo).preloadAllGIFData
-        
-        return Kingfisher<Image>.image(data: data, scale: scale, preloadAllGIFData: preloadAllGIFData)
+        let options = options ?? KingfisherEmptyOptionsInfo
+        return Kingfisher<Image>.image(
+            data: data,
+            scale: options.scaleFactor,
+            preloadAllGIFData: options.preloadAllGIFData,
+            onlyFirstFrame: options.onlyLoadFirstFrame)
     }
     }
 }
 }

+ 58 - 67
Sources/Image.swift

@@ -33,8 +33,8 @@ private var durationKey: Void?
 import UIKit
 import UIKit
 import MobileCoreServices
 import MobileCoreServices
 private var imageSourceKey: Void?
 private var imageSourceKey: Void?
-private var animatedImageDataKey: Void?
 #endif
 #endif
+private var animatedImageDataKey: Void?
 
 
 import ImageIO
 import ImageIO
 import CoreGraphics
 import CoreGraphics
@@ -46,6 +46,15 @@ import CoreImage
 
 
 // MARK: - Image Properties
 // MARK: - Image Properties
 extension Kingfisher where Base: Image {
 extension Kingfisher where Base: Image {
+    fileprivate(set) var animatedImageData: Data? {
+        get {
+            return objc_getAssociatedObject(base, &animatedImageDataKey) as? Data
+        }
+        set {
+            objc_setAssociatedObject(base, &animatedImageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
+        }
+    }
+    
     #if os(macOS)
     #if os(macOS)
     var cgImage: CGImage? {
     var cgImage: CGImage? {
         return base.cgImage(forProposedRect: nil, context: nil, hints: nil)
         return base.cgImage(forProposedRect: nil, context: nil, hints: nil)
@@ -105,15 +114,6 @@ extension Kingfisher where Base: Image {
         }
         }
     }
     }
     
     
-    fileprivate(set) var animatedImageData: Data? {
-    get {
-        return objc_getAssociatedObject(base, &animatedImageDataKey) as? Data
-    }
-    set {
-        objc_setAssociatedObject(base, &animatedImageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
-    }
-    }
-    
     var size: CGSize {
     var size: CGSize {
         return base.size
         return base.size
     }
     }
@@ -200,44 +200,13 @@ extension Kingfisher where Base: Image {
     
     
     // MARK: - GIF
     // MARK: - GIF
     public func gifRepresentation() -> Data? {
     public func gifRepresentation() -> Data? {
-        #if os(macOS)
-            return gifRepresentation(duration: 0.0, repeatCount: 0)
-        #else
-            return animatedImageData
-        #endif
+        return animatedImageData
     }
     }
-    
-    #if os(macOS)
-    func gifRepresentation(duration: TimeInterval, repeatCount: Int) -> Data? {
-        guard let images = images else {
-            return nil
-        }
-        
-        let frameCount = images.count
-        let gifDuration = duration <= 0.0 ? duration / Double(frameCount) : duration / Double(frameCount)
-        
-        let frameProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: gifDuration]]
-        let imageProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: repeatCount]]
-        
-        let data = NSMutableData()
-        
-        guard let destination = CGImageDestinationCreateWithData(data, kUTTypeGIF, frameCount, nil) else {
-            return nil
-        }
-        CGImageDestinationSetProperties(destination, imageProperties as CFDictionary)
-        
-        for image in images {
-            CGImageDestinationAddImage(destination, image.kf.cgImage!, frameProperties as CFDictionary)
-        }
-        
-        return CGImageDestinationFinalize(destination) ? data.copy() as? Data : nil
-    }
-    #endif
 }
 }
 
 
 // MARK: - Create images from data
 // MARK: - Create images from data
 extension Kingfisher where Base: Image {
 extension Kingfisher where Base: Image {
-    static func animated(with data: Data, scale: CGFloat = 1.0, duration: TimeInterval = 0.0, preloadAll: Bool) -> Image? {
+    static func animated(with data: Data, scale: CGFloat = 1.0, duration: TimeInterval = 0.0, preloadAll: Bool, onlyFirstFrame: Bool = false) -> Image? {
         
         
         func decode(from imageSource: CGImageSource, for options: NSDictionary) -> ([Image], TimeInterval)? {
         func decode(from imageSource: CGImageSource, for options: NSDictionary) -> ([Image], TimeInterval)? {
             
             
@@ -262,7 +231,7 @@ extension Kingfisher where Base: Image {
                 guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, options) else {
                 guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, options) else {
                     return nil
                     return nil
                 }
                 }
-                
+
                 if frameCount == 1 {
                 if frameCount == 1 {
                     // Single frame
                     // Single frame
                     gifDuration = Double.infinity
                     gifDuration = Double.infinity
@@ -277,6 +246,8 @@ extension Kingfisher where Base: Image {
                 }
                 }
                 
                 
                 images.append(Kingfisher<Image>.image(cgImage: imageRef, scale: scale, refImage: nil))
                 images.append(Kingfisher<Image>.image(cgImage: imageRef, scale: scale, refImage: nil))
+                
+                if onlyFirstFrame { break }
             }
             }
             
             
             return (images, gifDuration)
             return (images, gifDuration)
@@ -292,45 +263,65 @@ extension Kingfisher where Base: Image {
             guard let (images, gifDuration) = decode(from: imageSource, for: options) else {
             guard let (images, gifDuration) = decode(from: imageSource, for: options) else {
                 return nil
                 return nil
             }
             }
-            let image = Image(data: data)
-            image?.kf.images = images
-            image?.kf.duration = gifDuration
-            
+            let image: Image?
+            if onlyFirstFrame {
+                image = images.first
+            } else {
+                image = Image(data: data)
+                image?.kf.images = images
+                image?.kf.duration = gifDuration
+            }
+            image?.kf.animatedImageData = data
             return image
             return image
         #else
         #else
             
             
-            if preloadAll {
-                guard let (images, gifDuration) = decode(from: imageSource, for: options) else {
-                    return nil
-                }
-                let image = Kingfisher<Image>.animated(with: images, forDuration: duration <= 0.0 ? gifDuration : duration)
-                image?.kf.animatedImageData = data
-                return image
+            let image: Image?
+            if preloadAll || onlyFirstFrame {
+                guard let (images, gifDuration) = decode(from: imageSource, for: options) else { return nil }
+                image = onlyFirstFrame ? images.first : Kingfisher<Image>.animated(with: images, forDuration: duration <= 0.0 ? gifDuration : duration)
             } else {
             } else {
-                let image = Image(data: data)
-                image?.kf.animatedImageData = data
+                image = Image(data: data)
                 image?.kf.imageSource = ImageSource(ref: imageSource)
                 image?.kf.imageSource = ImageSource(ref: imageSource)
-                return image
             }
             }
+            image?.kf.animatedImageData = data
+            return image
         #endif
         #endif
     }
     }
     
     
-    static func image(data: Data, scale: CGFloat, preloadAllGIFData: Bool) -> Image? {
+    static func image(data: Data, scale: CGFloat, preloadAllGIFData: Bool, onlyFirstFrame: Bool) -> Image? {
         var image: Image?
         var image: Image?
         
         
         #if os(macOS)
         #if os(macOS)
             switch data.kf.imageFormat {
             switch data.kf.imageFormat {
-            case .JPEG: image = Image(data: data)
-            case .PNG: image = Image(data: data)
-            case .GIF: image = Kingfisher<Image>.animated(with: data, scale: scale, duration: 0.0, preloadAll: preloadAllGIFData)
-            case .unknown: image = Image(data: data)
+            case .JPEG:
+                image = Image(data: data)
+            case .PNG:
+                image = Image(data: data)
+            case .GIF:
+                image = Kingfisher<Image>.animated(
+                    with: data,
+                    scale: scale,
+                    duration: 0.0,
+                    preloadAll: preloadAllGIFData,
+                    onlyFirstFrame: onlyFirstFrame)
+            case .unknown:
+                image = Image(data: data)
             }
             }
         #else
         #else
             switch data.kf.imageFormat {
             switch data.kf.imageFormat {
-            case .JPEG: image = Image(data: data, scale: scale)
-            case .PNG: image = Image(data: data, scale: scale)
-            case .GIF: image = Kingfisher<Image>.animated(with: data, scale: scale, duration: 0.0, preloadAll: preloadAllGIFData)
-            case .unknown: image = Image(data: data, scale: scale)
+            case .JPEG:
+                image = Image(data: data, scale: scale)
+            case .PNG:
+                image = Image(data: data, scale: scale)
+            case .GIF:
+                image = Kingfisher<Image>.animated(
+                    with: data,
+                    scale: scale,
+                    duration: 0.0,
+                    preloadAll: preloadAllGIFData,
+                    onlyFirstFrame: onlyFirstFrame)
+            case .unknown:
+                image = Image(data: data, scale: scale)
             }
             }
         #endif
         #endif
         
         

+ 5 - 1
Sources/ImageProcessor.swift

@@ -116,7 +116,11 @@ public struct DefaultImageProcessor: ImageProcessor {
         case .image(let image):
         case .image(let image):
             return image
             return image
         case .data(let data):
         case .data(let data):
-            return Kingfisher<Image>.image(data: data, scale: options.scaleFactor, preloadAllGIFData: options.preloadAllGIFData)
+            return Kingfisher<Image>.image(
+                data: data,
+                scale: options.scaleFactor,
+                preloadAllGIFData: options.preloadAllGIFData,
+                onlyFirstFrame: options.onlyLoadFirstFrame)
         }
         }
     }
     }
 }
 }

+ 11 - 0
Sources/KingfisherOptionsInfo.swift

@@ -113,6 +113,12 @@ public enum KingfisherOptionsInfoItem {
     /// By setting this option, the placeholder image parameter of imageview extension method
     /// By setting this option, the placeholder image parameter of imageview extension method
     /// will be ignored and the current image will be kept while loading or downloading the new image.
     /// will be ignored and the current image will be kept while loading or downloading the new image.
     case keepCurrentImageWhileLoading
     case keepCurrentImageWhileLoading
+    
+    /// If set, Kingfisher will only load the first frame from a GIF file as a single image.
+    /// Loading a lot of GIFs may take too much memory. It will be useful when you want to display a 
+    /// static preview of the first frame from a GIF image.
+    /// This option will be ignored if the target image is not GIF.
+    case onlyLoadFirstFrame
 }
 }
 
 
 precedencegroup ItemComparisonPrecedence {
 precedencegroup ItemComparisonPrecedence {
@@ -141,6 +147,7 @@ func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Boo
     case (.processor(_), .processor(_)): return true
     case (.processor(_), .processor(_)): return true
     case (.cacheSerializer(_), .cacheSerializer(_)): return true
     case (.cacheSerializer(_), .cacheSerializer(_)): return true
     case (.keepCurrentImageWhileLoading, .keepCurrentImageWhileLoading): return true
     case (.keepCurrentImageWhileLoading, .keepCurrentImageWhileLoading): return true
+    case (.onlyLoadFirstFrame, .onlyLoadFirstFrame): return true
     default: return false
     default: return false
     }
     }
 }
 }
@@ -282,4 +289,8 @@ public extension Collection where Iterator.Element == KingfisherOptionsInfoItem
     public var keepCurrentImageWhileLoading: Bool {
     public var keepCurrentImageWhileLoading: Bool {
         return contains { $0 <== .keepCurrentImageWhileLoading }
         return contains { $0 <== .keepCurrentImageWhileLoading }
     }
     }
+    
+    public var onlyLoadFirstFrame: Bool {
+        return contains { $0 <== .onlyLoadFirstFrame }
+    }
 }
 }

+ 10 - 0
Tests/KingfisherTests/ImageExtensionTests.swift

@@ -105,4 +105,14 @@ class ImageExtensionTests: XCTestCase {
         XCTAssertEqual(image.kf.duration, image.kf.duration)
         XCTAssertEqual(image.kf.duration, image.kf.duration)
         XCTAssertEqual(image.kf.images!.count, image.kf.images!.count)
         XCTAssertEqual(image.kf.images!.count, image.kf.images!.count)
     }
     }
+    
+    func testLoadOnlyFirstFrame() {
+        let image = Kingfisher<Image>.animated(with: testImageGIFData,
+                                               scale: 1.0,
+                                               duration: 0.0,
+                                               preloadAll: true,
+                                               onlyFirstFrame: true)!
+        XCTAssertNotNil(image, "The image should be initiated.")
+        XCTAssertNil(image.kf.images, "The image should be nil")
+    }
 }
 }