From ece7f31393f73d0b31036b774acf876406ddfbb3 Mon Sep 17 00:00:00 2001 From: xspyhack Date: Sat, 18 May 2019 14:13:09 +0800 Subject: [PATCH 1/3] Improve animated image resizeing with Image/IO --- Sources/Image/ImageDrawing.swift | 74 --------------------------- Sources/Views/AnimatedImageView.swift | 20 ++++---- 2 files changed, 11 insertions(+), 83 deletions(-) diff --git a/Sources/Image/ImageDrawing.swift b/Sources/Image/ImageDrawing.swift index bf51f7ec9..29daf20f2 100644 --- a/Sources/Image/ImageDrawing.swift +++ b/Sources/Image/ImageDrawing.swift @@ -527,77 +527,3 @@ extension KingfisherWrapper where Base: Image { } #endif } - -extension CGImage: KingfisherCompatible {} -/// High Performance Image Resizing -/// @see https://nshipster.com/image-resizing/ -extension KingfisherWrapper where Base: CGImage { - var size: CGSize { - return CGSize(width: CGFloat(base.width), height: CGFloat(base.height)) - } - - /// Resizes `base` CGImage to a CGImage of new size, respecting the given content mode. - /// - /// - Parameters: - /// - targetSize: The target size in point. - /// - contentMode: Content mode of output image should be. - /// - Returns: A CGImage with new size. - #if os(iOS) || os(tvOS) - public func resize(to size: CGSize, for contentMode: UIView.ContentMode) -> CGImage { - switch contentMode { - case .scaleAspectFit: - return resize(to: size, for: .aspectFit) - case .scaleAspectFill: - return resize(to: size, for: .aspectFill) - default: - return resize(to: size) - } - } - #endif - - // MARK: - Resize - /// Resizes `base` CGImage to a CGImage with new size. - /// - /// - Parameter size: The target size in point. - /// - Returns: A CGImage with new size. - public func resize(to size: CGSize) -> CGImage { - let alphaInfo = base.alphaInfo.rawValue & CGBitmapInfo.alphaInfoMask.rawValue - var hasAlpha = false - if alphaInfo == CGImageAlphaInfo.premultipliedLast.rawValue - || alphaInfo == CGImageAlphaInfo.premultipliedFirst.rawValue - || alphaInfo == CGImageAlphaInfo.first.rawValue - || alphaInfo == CGImageAlphaInfo.last.rawValue { - hasAlpha = true - } - - var bitmapInfo = CGImageByteOrderInfo.order32Little.rawValue - bitmapInfo |= hasAlpha ? CGImageAlphaInfo.premultipliedFirst.rawValue : CGImageAlphaInfo.noneSkipFirst.rawValue - - guard let context = CGContext(data: nil, - width: Int(size.width), - height: Int(size.height), - bitsPerComponent: base.bitsPerComponent, - bytesPerRow: base.bytesPerRow, - space: base.colorSpace ?? CGColorSpaceCreateDeviceRGB(), - bitmapInfo: bitmapInfo) else - { - return base - } - - let rect = CGRect(origin: .zero, size: size) - context.interpolationQuality = .high - context.draw(base, in: rect) - return context.makeImage() ?? base - } - - /// Resizes `base` CGImage to a CGImage of new size, respecting the given content mode. - /// - /// - Parameters: - /// - targetSize: The target size in point. - /// - contentMode: Content mode of output image should be. - /// - Returns: A CGImage with new size. - public func resize(to targetSize: CGSize, for contentMode: ContentMode) -> CGImage { - let newSize = size.kf.resize(to: targetSize, for: contentMode) - return resize(to: newSize) - } -} diff --git a/Sources/Views/AnimatedImageView.swift b/Sources/Views/AnimatedImageView.swift index 8db3d490a..02529be92 100644 --- a/Sources/Views/AnimatedImageView.swift +++ b/Sources/Views/AnimatedImageView.swift @@ -488,18 +488,20 @@ extension AnimatedImageView { } private func loadFrame(at index: Int) -> UIImage? { - guard let image = CGImageSourceCreateImageAtIndex(imageSource, index, nil) else { + let options: [CFString: Any] = [ + kCGImageSourceCreateThumbnailFromImageIfAbsent: true, + kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceShouldCacheImmediately: true, + kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) + ] + + guard let cgImage = CGImageSourceCreateImageAtIndex(imageSource, + index, + needsPrescaling ? options as CFDictionary : nil) else { return nil } - let scaledImage: CGImage - if needsPrescaling, size != .zero { - scaledImage = image.kf.resize(to: size, for: contentMode) - } else { - scaledImage = image - } - - return Image(cgImage: scaledImage) + return Image(cgImage: cgImage) } private func updatePreloadedFrames() { From ec87d54a72aa41e9e66fa01ea1aa064d6dd0a16c Mon Sep 17 00:00:00 2001 From: xspyhack Date: Sat, 18 May 2019 14:22:44 +0800 Subject: [PATCH 2/3] Supports background gif decoding --- Sources/Views/AnimatedImageView.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sources/Views/AnimatedImageView.swift b/Sources/Views/AnimatedImageView.swift index 02529be92..9c021b91d 100644 --- a/Sources/Views/AnimatedImageView.swift +++ b/Sources/Views/AnimatedImageView.swift @@ -120,6 +120,10 @@ open class AnimatedImageView: UIImageView { /// Default is `true`. public var needsPrescaling = true + /// Decode the GIF frames in background thread before using. It will decode frames data and do a off-screen + /// rendering to extract pixel information in background. This can reduce the main thread CPU usage. + public var backgroundDecode = true + /// The animation timer's run loop mode. Default is `RunLoop.Mode.common`. /// Set this property to `RunLoop.Mode.default` will make the animation pause during UIScrollView scrolling. public var runLoopMode = KFRunLoopModeCommon { @@ -251,6 +255,7 @@ open class AnimatedImageView: UIImageView { preloadQueue: preloadQueue) animator.delegate = self animator.needsPrescaling = needsPrescaling + animator.backgroundDecode = backgroundDecode animator.prepareFramesAsynchronously() self.animator = animator } @@ -361,6 +366,9 @@ extension AnimatedImageView { var isFinished: Bool = false var needsPrescaling = true + + var backgroundDecode = true + weak var delegate: AnimatorDelegate? // Total duration of one animation loop @@ -501,7 +509,8 @@ extension AnimatedImageView { return nil } - return Image(cgImage: cgImage) + let image = Image(cgImage: cgImage) + return backgroundDecode ? image.kf.decoded : image } private func updatePreloadedFrames() { From bcd68ac9f2b6571ae9144ba145ffc2cadf9e9e07 Mon Sep 17 00:00:00 2001 From: xspyhack Date: Tue, 21 May 2019 00:25:24 +0800 Subject: [PATCH 3/3] Ignoring resizing animated frame when size is zero --- Sources/Views/AnimatedImageView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Views/AnimatedImageView.swift b/Sources/Views/AnimatedImageView.swift index 9c021b91d..2dfc9ce37 100644 --- a/Sources/Views/AnimatedImageView.swift +++ b/Sources/Views/AnimatedImageView.swift @@ -503,9 +503,10 @@ extension AnimatedImageView { kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height) ] + let resize = needsPrescaling && size != .zero guard let cgImage = CGImageSourceCreateImageAtIndex(imageSource, index, - needsPrescaling ? options as CFDictionary : nil) else { + resize ? options as CFDictionary : nil) else { return nil }