scale image with ImageLoader to offload main thread
This commit is contained in:
@@ -729,7 +729,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
chapter: currentChapter,
|
||||
volume: Int(metadata.chapters[currentChapter - 1].volume),
|
||||
page: currentPage,
|
||||
path: currentPath)
|
||||
path: currentPath),
|
||||
scaling: .scaleAspectFit
|
||||
) {
|
||||
[weak self] image in
|
||||
self?.imageView.image = image
|
||||
@@ -856,7 +857,8 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
|
||||
func setupImageView() {
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
imageView.contentMode = UIView.ContentMode.scaleAspectFit
|
||||
// Scaling is done when the image is loaded to avoid scaling on main thread
|
||||
imageView.contentMode = .center
|
||||
imageView.clipsToBounds = true
|
||||
|
||||
readerView.addSubview(imageView)
|
||||
@@ -870,12 +872,13 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func changeImage(turn: PageTurn) {
|
||||
let scaling = UIView.ContentMode.scaleAspectFit
|
||||
var (chapter, page) = getChapterAndPageFromTurn(
|
||||
chapter: currentChapter, page: currentPage, turn: turn)
|
||||
if (chapter, page) == (currentChapter, currentPage) { return }
|
||||
var vol = Int(metadata.chapters[chapter - 1].volume)
|
||||
if let path = getImagePath(chapter: chapter, volume: vol, page: page, path: currentPath) {
|
||||
imageLoader.loadImage(at: path) {
|
||||
imageLoader.loadImage(at: path, scaling: scaling) {
|
||||
[weak self] image in
|
||||
self?.imageView.image = image
|
||||
}
|
||||
@@ -892,7 +895,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
if let path = getImagePath(
|
||||
chapter: chapter, volume: vol, page: page, path: currentPath)
|
||||
{
|
||||
imageLoader.preloadImage(at: path)
|
||||
imageLoader.preloadImage(at: path, scaling: scaling)
|
||||
} else {
|
||||
print("could not preload image")
|
||||
}
|
||||
@@ -918,9 +921,14 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
Chapter \(currentChapter!) of \(Int(metadata.last_chapter))
|
||||
Page \(currentPage!) of \(metadata.chapters[currentChapter - 1].pages)
|
||||
"""
|
||||
if let image = imageView.image {
|
||||
if let size = imageLoader.size[
|
||||
getImagePath(
|
||||
chapter: currentChapter, volume: Int(metadata.chapters[currentChapter - 1].volume),
|
||||
page: currentPage!, path: currentPath
|
||||
).path]
|
||||
{
|
||||
text =
|
||||
text + "\nImage size: \(Int(image.size.width))x\(Int(image.size.height))"
|
||||
text + "\nImage size: \(Int(size.width))x\(Int(size.height))"
|
||||
}
|
||||
info.text = text
|
||||
}
|
||||
@@ -928,10 +936,10 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
func getImagePath(chapter: Int, volume: Int, page: Int, path: URL) -> URL! {
|
||||
let modernPath = path.appendingPathComponent(String(format: "volume_%04d", volume))
|
||||
.appendingPathComponent(String(format: "chapter_%04d_page_%04d.png", chapter, page))
|
||||
if fileManager.fileExists(atPath: modernPath.path) {
|
||||
return modernPath
|
||||
}
|
||||
return nil
|
||||
// if fileManager.fileExists(atPath: modernPath.path) {
|
||||
return modernPath
|
||||
// }
|
||||
// return nil
|
||||
}
|
||||
|
||||
func getChapterAndPageFromTurn(chapter: Int, page: Int, turn: PageTurn) -> (Int, Int) {
|
||||
@@ -977,13 +985,15 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
func setImages(path: URL, metadata: Metadata) {
|
||||
let scaling = UIView.ContentMode.scaleAspectFit
|
||||
var directories: [URL] = []
|
||||
if currentPage != nil && currentChapter != nil {
|
||||
var vol = Int(metadata.chapters[currentChapter - 1].volume)
|
||||
imageLoader.loadImage(
|
||||
at: getImagePath(
|
||||
chapter: currentChapter, volume: vol, page: currentPage, path: currentPath
|
||||
)
|
||||
),
|
||||
scaling: scaling
|
||||
) {
|
||||
[weak self] image in
|
||||
self?.imageView.image = image
|
||||
@@ -997,7 +1007,7 @@ class ViewController: UIViewController, UIGestureRecognizerDelegate {
|
||||
if let path = getImagePath(
|
||||
chapter: chapter, volume: vol, page: page, path: currentPath)
|
||||
{
|
||||
imageLoader.preloadImage(at: path)
|
||||
imageLoader.preloadImage(at: path, scaling: scaling)
|
||||
} else {
|
||||
print("could not preload image")
|
||||
}
|
||||
@@ -1103,6 +1113,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
||||
cell.imageView.image = comics[indexPath.item].cover
|
||||
return cell
|
||||
} else if collectionView == scrollingCollectionView {
|
||||
let scaling = UIView.ContentMode.scaleAspectFill
|
||||
let cell =
|
||||
collectionView.dequeueReusableCell(
|
||||
withReuseIdentifier: "ScrollingImageCell", for: indexPath)
|
||||
@@ -1116,7 +1127,9 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
||||
volume: Int(metadata.chapters[chapter - 1].volume),
|
||||
page: page, path: currentPath)
|
||||
{
|
||||
imageLoader.loadImage(at: url) { image in cell.imageView.image = image }
|
||||
imageLoader.loadImage(at: url, scaling: scaling) { image in
|
||||
cell.imageView.image = image
|
||||
}
|
||||
for _ in 0...preloadCount {
|
||||
(chapter, page) = getChapterAndPageFromTurn(
|
||||
chapter: chapter, page: page, turn: .next)
|
||||
@@ -1125,7 +1138,7 @@ extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFl
|
||||
volume: Int(metadata.chapters[chapter - 1].volume),
|
||||
page: page, path: currentPath)
|
||||
{
|
||||
imageLoader.preloadImage(at: url)
|
||||
imageLoader.preloadImage(at: url, scaling: scaling)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
@@ -1204,7 +1217,8 @@ class ScrollingImageCell: UICollectionViewCell {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
imageView.contentMode = .scaleAspectFill
|
||||
// Scaling is done when the image is loaded to avoid scaling on main thread
|
||||
imageView.contentMode = .center
|
||||
imageView.clipsToBounds = true
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(imageView)
|
||||
@@ -1248,17 +1262,18 @@ class ComicImageCell: UICollectionViewCell {
|
||||
class ImageLoader {
|
||||
private let cache = NSCache<NSString, UIImage>()
|
||||
private var loadingTasks: [String: [((UIImage?) -> Void)]] = [:]
|
||||
var size: [String: CGSize] = [:]
|
||||
|
||||
init() {
|
||||
// 128 MiB
|
||||
cache.totalCostLimit = 128 * 1024 * 1024
|
||||
}
|
||||
|
||||
func preloadImage(at path: URL) {
|
||||
loadImage(at: path, completion: nil)
|
||||
func preloadImage(at path: URL, scaling: UIView.ContentMode) {
|
||||
loadImage(at: path, scaling: scaling, completion: nil)
|
||||
}
|
||||
|
||||
func loadImage(at path: URL, completion: ((UIImage?) -> Void)?) {
|
||||
func loadImage(at path: URL, scaling: UIView.ContentMode, completion: ((UIImage?) -> Void)?) {
|
||||
if let cached = cache.object(forKey: path.path as NSString) {
|
||||
completion?(cached)
|
||||
return
|
||||
@@ -1275,14 +1290,13 @@ class ImageLoader {
|
||||
|
||||
queue.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
let image = UIImage(contentsOfFile: path.path)
|
||||
|
||||
if let image = image, let cost = imageByteSize(image) {
|
||||
self.cache.setObject(image, forKey: path.path as NSString, cost: cost)
|
||||
} else {
|
||||
print("no image to cache found")
|
||||
}
|
||||
guard var image = UIImage(contentsOfFile: path.path) else { return }
|
||||
self.size[path.path] = image.size
|
||||
let scaledSize = aspectSize(
|
||||
for: image.size, in: UIScreen.main.bounds.size, scaling: scaling)
|
||||
image = resizeImage(image, to: scaledSize)
|
||||
guard let cost = imageByteSize(image) else { return }
|
||||
self.cache.setObject(image, forKey: path.path as NSString, cost: cost)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.loadingTasks[path.path]?.forEach { $0(image) }
|
||||
@@ -1300,3 +1314,29 @@ func imageByteSize(_ image: UIImage) -> Int? {
|
||||
guard let cgImage = image.cgImage else { return nil }
|
||||
return cgImage.bytesPerRow * cgImage.height
|
||||
}
|
||||
|
||||
func resizeImage(_ image: UIImage, to size: CGSize) -> UIImage {
|
||||
UIGraphicsBeginImageContextWithOptions(size, true, 0.0)
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return scaledImage!
|
||||
}
|
||||
|
||||
func aspectSize(for imageSize: CGSize, in boundingSize: CGSize, scaling: UIView.ContentMode)
|
||||
-> CGSize
|
||||
{
|
||||
let widthRatio = boundingSize.width / imageSize.width
|
||||
let heightRatio = boundingSize.height / imageSize.height
|
||||
let scale: CGFloat =
|
||||
switch scaling {
|
||||
case .scaleAspectFit: min(widthRatio, heightRatio)
|
||||
case .scaleAspectFill: max(widthRatio, heightRatio)
|
||||
default: 1
|
||||
}
|
||||
|
||||
return CGSize(
|
||||
width: imageSize.width * scale,
|
||||
height: imageSize.height * scale
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user