forked from shadowfacts/Tusker
Cache scaled images
This commit is contained in:
parent
6dee0957ea
commit
04a6fe807e
|
@ -11,7 +11,7 @@ import Cache
|
||||||
|
|
||||||
class ImageCache {
|
class ImageCache {
|
||||||
|
|
||||||
static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24))
|
static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24), desiredSize: CGSize(width: 50, height: 50))
|
||||||
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
static let headers = ImageCache(name: "Headers", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
||||||
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
|
static let attachments = ImageCache(name: "Attachments", memoryExpiry: .seconds(60 * 2))
|
||||||
static let emojis = ImageCache(name: "Emojis", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
static let emojis = ImageCache(name: "Emojis", memoryExpiry: .seconds(60 * 5), diskExpiry: .seconds(60 * 60))
|
||||||
|
@ -28,8 +28,10 @@ class ImageCache {
|
||||||
|
|
||||||
private var backgroundQueue = DispatchQueue(label: "ImageCache completion queue", qos: .default)
|
private var backgroundQueue = DispatchQueue(label: "ImageCache completion queue", qos: .default)
|
||||||
|
|
||||||
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry? = nil) {
|
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry? = nil, desiredSize: CGSize? = nil) {
|
||||||
self.cache = ImageDataCache(name: name, memoryExpiry: memoryExpiry, diskExpiry: diskExpiry, storeOriginalDataInMemory: diskExpiry == nil)
|
// todo: might not always want to use UIScreen.main for this, e.g. Catalyst?
|
||||||
|
let pixelSize = desiredSize?.applying(.init(scaleX: UIScreen.main.scale, y: UIScreen.main.scale))
|
||||||
|
self.cache = ImageDataCache(name: name, memoryExpiry: memoryExpiry, diskExpiry: diskExpiry, storeOriginalDataInMemory: diskExpiry == nil, desiredPixelSize: pixelSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
func get(_ url: URL, completion: ((Data?, UIImage?) -> Void)?) -> Request? {
|
func get(_ url: URL, completion: ((Data?, UIImage?) -> Void)?) -> Request? {
|
||||||
|
@ -49,9 +51,8 @@ class ImageCache {
|
||||||
return group.addCallback(completion)
|
return group.addCallback(completion)
|
||||||
} else {
|
} else {
|
||||||
let group = RequestGroup(url: url) { (data, image) in
|
let group = RequestGroup(url: url) { (data, image) in
|
||||||
if let data = data,
|
if let data = data {
|
||||||
let image = UIImage(data: data) {
|
try? self.cache.set(key, data: data)
|
||||||
try? self.cache.set(key, data: data, image: image)
|
|
||||||
}
|
}
|
||||||
self.groups.removeValueWithoutReturning(forKey: url)
|
self.groups.removeValueWithoutReturning(forKey: url)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,9 @@ class ImageDataCache {
|
||||||
private let disk: DiskStorage<Data>?
|
private let disk: DiskStorage<Data>?
|
||||||
|
|
||||||
private let storeOriginalDataInMemory: Bool
|
private let storeOriginalDataInMemory: Bool
|
||||||
|
private let desiredPixelSize: CGSize?
|
||||||
|
|
||||||
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry?, storeOriginalDataInMemory: Bool) {
|
init(name: String, memoryExpiry: Expiry, diskExpiry: Expiry?, storeOriginalDataInMemory: Bool, desiredPixelSize: CGSize?) {
|
||||||
let memoryConfig = MemoryConfig(expiry: memoryExpiry)
|
let memoryConfig = MemoryConfig(expiry: memoryExpiry)
|
||||||
self.memory = MemoryStorage(config: memoryConfig)
|
self.memory = MemoryStorage(config: memoryConfig)
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ class ImageDataCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.storeOriginalDataInMemory = storeOriginalDataInMemory
|
self.storeOriginalDataInMemory = storeOriginalDataInMemory
|
||||||
|
self.desiredPixelSize = desiredPixelSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func has(_ key: String) throws -> Bool {
|
func has(_ key: String) throws -> Bool {
|
||||||
|
@ -42,10 +44,9 @@ class ImageDataCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
func get(_ key: String) throws -> Entry? {
|
func get(_ key: String) throws -> Entry? {
|
||||||
if try memory.existsObject(forKey: key) {
|
if let memoryEntry = try? memory.object(forKey: key) {
|
||||||
return try! memory.object(forKey: key)
|
return memoryEntry
|
||||||
} else if let disk = self.disk,
|
} else if let disk = self.disk,
|
||||||
try disk.existsObject(forKey: key),
|
|
||||||
let data = try? disk.object(forKey: key),
|
let data = try? disk.object(forKey: key),
|
||||||
let image = UIImage(data: data) {
|
let image = UIImage(data: data) {
|
||||||
return Entry(data: data, image: image)
|
return Entry(data: data, image: image)
|
||||||
|
@ -62,7 +63,9 @@ class ImageDataCache {
|
||||||
return try get(key)?.data
|
return try get(key)?.data
|
||||||
}
|
}
|
||||||
|
|
||||||
func set(_ key: String, data: Data, image: UIImage) throws {
|
func set(_ key: String, data: Data) throws {
|
||||||
|
guard let image = scaleImageIfDesired(data: data) else { return }
|
||||||
|
|
||||||
let entry = Entry(data: storeOriginalDataInMemory ? data : nil, image: image)
|
let entry = Entry(data: storeOriginalDataInMemory ? data : nil, image: image)
|
||||||
memory.setObject(entry, forKey: key)
|
memory.setObject(entry, forKey: key)
|
||||||
|
|
||||||
|
@ -76,6 +79,27 @@ class ImageDataCache {
|
||||||
try? disk?.removeAll()
|
try? disk?.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func scaleImageIfDesired(data: Data) -> UIImage? {
|
||||||
|
guard let desiredPixelSize = desiredPixelSize,
|
||||||
|
let source = CGImageSourceCreateWithData(data as CFData, [kCGImageSourceShouldCache: false] as CFDictionary) else {
|
||||||
|
return UIImage(data: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxDimension = max(desiredPixelSize.width, desiredPixelSize.height)
|
||||||
|
let downsampleOptions = [
|
||||||
|
kCGImageSourceCreateThumbnailFromImageAlways: true,
|
||||||
|
kCGImageSourceShouldCacheImmediately: true,
|
||||||
|
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||||
|
kCGImageSourceThumbnailMaxPixelSize: maxDimension
|
||||||
|
] as CFDictionary
|
||||||
|
|
||||||
|
if let downsampled = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) {
|
||||||
|
return UIImage(cgImage: downsampled)
|
||||||
|
} else {
|
||||||
|
return UIImage(data: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ImageDataCache {
|
extension ImageDataCache {
|
||||||
|
|
Loading…
Reference in New Issue