ImageLoader: add method for scaling images, cache unscaled images, and
set image early.
This commit was merged in pull request #17.
This commit is contained in:
@@ -1549,6 +1549,7 @@ enum Rotation {
|
||||
}
|
||||
|
||||
class ImageLoader {
|
||||
private let cache = NSCache<NSString, UIImage>()
|
||||
private let portraitFitCache = NSCache<NSString, UIImage>()
|
||||
private let landscapeFitCache = NSCache<NSString, UIImage>()
|
||||
private let portraitFillCache = NSCache<NSString, UIImage>()
|
||||
@@ -1559,6 +1560,7 @@ class ImageLoader {
|
||||
|
||||
init() {
|
||||
// 128 MiB
|
||||
cache.totalCostLimit = 128 * 1024 * 1024
|
||||
portraitFitCache.totalCostLimit = 128 * 1024 * 1024
|
||||
landscapeFitCache.totalCostLimit = 128 * 1024 * 1024
|
||||
portraitFillCache.totalCostLimit = 128 * 1024 * 1024
|
||||
@@ -1594,13 +1596,9 @@ class ImageLoader {
|
||||
if rotation == .horizontal, let cached = landscapeFillCache.object(forKey: key as NSString) {
|
||||
completion?(cached); return
|
||||
}
|
||||
} else {
|
||||
print("this should not be reachable")
|
||||
return
|
||||
}
|
||||
} else { assertionFailure() }
|
||||
|
||||
if let completion = completion {
|
||||
assert(loadingTasks[key] == nil)
|
||||
loadingTasks[key] = completion
|
||||
}
|
||||
if let existing = activeTokens[key], !existing.isCancelled {
|
||||
@@ -1613,86 +1611,102 @@ class ImageLoader {
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
func finish(_ image: UIImage?) {
|
||||
func finish() {
|
||||
DispatchQueue.main.async {
|
||||
guard self.activeTokens[key] === token else { return }
|
||||
self.activeTokens.removeValue(forKey: key)
|
||||
if let image = image {
|
||||
if let callback = self.loadingTasks.removeValue(forKey: key) {
|
||||
callback(image)
|
||||
}
|
||||
self.loadingTasks.removeValue(forKey: key)
|
||||
}
|
||||
}
|
||||
func setImage(_ image: UIImage) {
|
||||
DispatchQueue.main.async {
|
||||
if let callback = self.loadingTasks[key] {
|
||||
callback(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if token.isCancelled { finish(nil); return }
|
||||
if token.isCancelled { finish(); return }
|
||||
|
||||
var extractedData = Data()
|
||||
if self.archives.object(forKey: archiveURL.lastPathComponent as NSString) == nil {
|
||||
let archive = try! Archive(url: archiveURL, accessMode: .read)
|
||||
self.archives.setObject(archive, forKey: archiveURL.lastPathComponent as NSString)
|
||||
}
|
||||
let archive = self.archives.object(forKey: archiveURL.lastPathComponent as NSString)!
|
||||
_ = try! archive.extract(archive[filename]!) { data in
|
||||
extractedData.append(data)
|
||||
func extractData() -> Data {
|
||||
var extractedData = Data()
|
||||
if self.archives.object(forKey: archiveURL.lastPathComponent as NSString) == nil {
|
||||
let archive = try! Archive(url: archiveURL, accessMode: .read)
|
||||
self.archives.setObject(archive, forKey: archiveURL.lastPathComponent as NSString)
|
||||
}
|
||||
let archive = self.archives.object(forKey: archiveURL.lastPathComponent as NSString)!
|
||||
_ = try! archive.extract(archive[filename]!) { data in
|
||||
extractedData.append(data)
|
||||
}
|
||||
return extractedData
|
||||
}
|
||||
|
||||
if token.isCancelled { finish(nil); return }
|
||||
if token.isCancelled { finish(); return }
|
||||
|
||||
guard let image = UIImage(data: extractedData) else {
|
||||
finish(nil)
|
||||
guard let image = cache.object(forKey: key as NSString) ?? UIImage(data: extractData()) else {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
var finalImage: UIImage!
|
||||
self.cache.setObject(image, forKey: filename as NSString, cost: imageByteSize(image)!)
|
||||
|
||||
if token.isCancelled { finish(nil); return }
|
||||
if token.isCancelled { finish(); return }
|
||||
|
||||
// If you turn pages fast, completion will not be nil and as such only the needed scaled image should be prepared
|
||||
if completion == nil || rotation == .vertical {
|
||||
let vertical = CGSize(
|
||||
width: min(screenSize.width, screenSize.height),
|
||||
height: max(screenSize.height, screenSize.width)
|
||||
)
|
||||
let vScaledSize = aspectSize(for: image.size, in: vertical, scaling: scaling)
|
||||
let vScaleImage = resizeImage(image, to: vScaledSize)
|
||||
let cost = imageByteSize(vScaleImage)!
|
||||
if scaling == .scaleAspectFit {
|
||||
self.portraitFitCache.setObject(vScaleImage, forKey: filename as NSString, cost: cost)
|
||||
} else if scaling == .scaleAspectFill {
|
||||
self.portraitFillCache.setObject(vScaleImage, forKey: filename as NSString, cost: cost)
|
||||
} else {
|
||||
print("this should be unreachable")
|
||||
return
|
||||
}
|
||||
if rotation == .vertical { finalImage = vScaleImage }
|
||||
}
|
||||
let scaledImage = scaleAndCacheImage(
|
||||
image: image,
|
||||
orientation: rotation,
|
||||
screenSize: screenSize,
|
||||
scaling: scaling,
|
||||
filename: key,
|
||||
)
|
||||
|
||||
if token.isCancelled { finish(nil); return }
|
||||
if token.isCancelled { finish(); return }
|
||||
|
||||
if completion == nil || rotation == .horizontal {
|
||||
let horizontal = CGSize(
|
||||
width: max(screenSize.width, screenSize.height),
|
||||
height: min(screenSize.height, screenSize.width)
|
||||
)
|
||||
let hScaledSize = aspectSize(for: image.size, in: horizontal, scaling: scaling)
|
||||
let hScaleImage = resizeImage(image, to: hScaledSize)
|
||||
let cost = imageByteSize(hScaleImage)!
|
||||
if scaling == .scaleAspectFit {
|
||||
self.landscapeFitCache.setObject(hScaleImage, forKey: filename as NSString, cost: cost)
|
||||
} else if scaling == .scaleAspectFill {
|
||||
self.landscapeFillCache.setObject(hScaleImage, forKey: filename as NSString, cost: cost)
|
||||
} else {
|
||||
print("this should be unreachable")
|
||||
return
|
||||
}
|
||||
if rotation == .horizontal { finalImage = hScaleImage }
|
||||
}
|
||||
setImage(scaledImage)
|
||||
|
||||
finish(finalImage)
|
||||
let otherOrientation: Rotation = rotation == .vertical ? .horizontal : .vertical
|
||||
|
||||
_ = scaleAndCacheImage(
|
||||
image: image,
|
||||
orientation: otherOrientation,
|
||||
screenSize: screenSize,
|
||||
scaling: scaling,
|
||||
filename: key,
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
func scaleAndCacheImage(image: UIImage, orientation: Rotation, screenSize: CGSize, scaling: UIView.ContentMode, filename: String) -> UIImage {
|
||||
let resizeTo: CGSize = if orientation == .vertical {
|
||||
CGSize(
|
||||
width: min(screenSize.width, screenSize.height),
|
||||
height: max(screenSize.height, screenSize.width)
|
||||
)
|
||||
} else {
|
||||
CGSize(
|
||||
width: max(screenSize.width, screenSize.height),
|
||||
height: min(screenSize.height, screenSize.width)
|
||||
)
|
||||
}
|
||||
let scaledSize = aspectSize(for: image.size, in: resizeTo, scaling: scaling)
|
||||
let scaledImage = resizeImage(image, to: scaledSize)
|
||||
let cost = imageByteSize(scaledImage)!
|
||||
switch ((orientation, scaling)) {
|
||||
case (.vertical, .scaleAspectFit):
|
||||
self.portraitFitCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
|
||||
case (.vertical, .scaleAspectFill):
|
||||
self.portraitFillCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
|
||||
case (.horizontal, .scaleAspectFit):
|
||||
self.landscapeFitCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
|
||||
case (.horizontal, .scaleAspectFill):
|
||||
self.landscapeFillCache.setObject(scaledImage, forKey: filename as NSString, cost: cost)
|
||||
default:
|
||||
assertionFailure()
|
||||
}
|
||||
return scaledImage
|
||||
}
|
||||
|
||||
func setActiveWindow(_ paths: [String]) {
|
||||
let desired = Set(paths)
|
||||
for (key, token) in activeTokens where !desired.contains(key) {
|
||||
|
||||
Reference in New Issue
Block a user