Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve animated image resizing (again?) and supports background decoding. #1189

Merged
merged 3 commits into from
May 21, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 0 additions & 74 deletions Sources/Image/ImageDrawing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
29 changes: 20 additions & 9 deletions Sources/Views/AnimatedImageView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -251,6 +255,7 @@ open class AnimatedImageView: UIImageView {
preloadQueue: preloadQueue)
animator.delegate = self
animator.needsPrescaling = needsPrescaling
animator.backgroundDecode = backgroundDecode
animator.prepareFramesAsynchronously()
self.animator = animator
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -488,18 +496,21 @@ 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,
onevcat marked this conversation as resolved.
Show resolved Hide resolved
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)
let image = Image(cgImage: cgImage)
return backgroundDecode ? image.kf.decoded : image
}

private func updatePreloadedFrames() {
Expand Down