diff --git a/ImageViewer/ViewController.swift b/ImageViewer/ViewController.swift index 9057eae..51eb0d2 100644 --- a/ImageViewer/ViewController.swift +++ b/ImageViewer/ViewController.swift @@ -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() 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 + ) +}