From 2d8e2f08243a44774a8a564020224918a3b21c02 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Fri, 12 May 2023 21:40:17 -0400 Subject: [PATCH] Fix hitches due to AttachmentView not using pre-prepared images --- Tusker/ImageGrayscalifier.swift | 13 ++- Tusker/Views/Attachments/AttachmentView.swift | 106 +++++++++++------- 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/Tusker/ImageGrayscalifier.swift b/Tusker/ImageGrayscalifier.swift index 659cebf2..548d3395 100644 --- a/Tusker/ImageGrayscalifier.swift +++ b/Tusker/ImageGrayscalifier.swift @@ -9,7 +9,7 @@ import UIKit struct ImageGrayscalifier { - static let queue = DispatchQueue(label: "ImageGrayscalifier", qos: .default) + static let queue = DispatchQueue(label: "ImageGrayscalifier", qos: .userInitiated) private static let context = CIContext() private static let cache = NSCache() @@ -24,6 +24,17 @@ struct ImageGrayscalifier { } } + static func convert(url: URL?, image: UIImage) -> UIImage? { + if let url, + let cached = cache.object(forKey: url as NSURL) { + return cached + } + guard let cgImage = image.cgImage else { + return nil + } + return doConvert(CIImage(cgImage: cgImage), url: url) + } + static func convert(url: URL?, data: Data) -> UIImage? { if let url = url, let cached = cache.object(forKey: url as NSURL) { diff --git a/Tusker/Views/Attachments/AttachmentView.swift b/Tusker/Views/Attachments/AttachmentView.swift index 5cfeb23d..01ef3479 100644 --- a/Tusker/Views/Attachments/AttachmentView.swift +++ b/Tusker/Views/Attachments/AttachmentView.swift @@ -75,9 +75,7 @@ class AttachmentView: GIFImageView { gifPlaybackModeChanged() if isGrayscale != Preferences.shared.grayscaleImages { - ImageGrayscalifier.queue.async { - self.displayImage() - } + self.displayImage() } if getBadges().isEmpty != Preferences.shared.showAttachmentBadges { @@ -189,51 +187,57 @@ class AttachmentView: GIFImageView { func loadImage() { let attachmentURL = attachment.url - attachmentRequest = ImageCache.attachments.get(attachmentURL) { [weak self] (data, _) in - guard let self = self, let data = data else { return } + attachmentRequest = ImageCache.attachments.get(attachmentURL) { [weak self] (data, image) in + guard let self = self, + self.attachment.url == attachmentURL else { + return + } + DispatchQueue.main.async { self.attachmentRequest = nil - } - if self.attachment.url.pathExtension == "gif" { - self.source = .gifData(attachmentURL, data) - let controller = GIFController(gifData: data) - DispatchQueue.main.async { - controller.attach(to: self) - if self.autoplayGifs { - controller.startAnimating() - } - } - if !self.autoplayGifs { + if attachmentURL.pathExtension == "gif", + let data { + self.source = .gifData(attachmentURL, data, image) + if self.autoplayGifs { + let controller = GIFController(gifData: data) + controller.attach(to: self) + controller.startAnimating() + } else { + self.displayImage() + } + } else if let image { + self.source = .image(attachmentURL, image) self.displayImage() } - - } else { - self.source = .imageData(attachmentURL, data) - self.displayImage() } } } func loadVideo() { if let previewURL = self.attachment.previewURL { - attachmentRequest = ImageCache.attachments.get(previewURL, completion: { [weak self] (data, _ )in - guard let self = self, let data = data else { return } + attachmentRequest = ImageCache.attachments.get(previewURL, completion: { [weak self] (_, image) in + guard let self, let image else { return } DispatchQueue.main.async { self.attachmentRequest = nil - self.source = .imageData(previewURL, data) + self.source = .image(previewURL, image) self.displayImage() } }) } else { let attachmentURL = self.attachment.url - AttachmentView.queue.async { + AttachmentView.queue.async { [weak self] in let asset = AVURLAsset(url: attachmentURL) let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return } - self.source = .cgImage(attachmentURL, image) - self.displayImage() + UIImage(cgImage: image).prepareForDisplay { [weak self] image in + DispatchQueue.main.async { [weak self] in + guard let self, let image else { return } + self.source = .image(attachmentURL, image) + self.displayImage() + } + } } } @@ -276,8 +280,10 @@ class AttachmentView: GIFImageView { let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return } - self.source = .cgImage(attachmentURL, image) - self.displayImage() + DispatchQueue.main.async { + self.source = .cgImage(attachmentURL, image) + self.displayImage() + } } let gifvView = GifvAttachmentView(asset: asset, gravity: .resizeAspectFill) @@ -295,33 +301,51 @@ class AttachmentView: GIFImageView { ]) } + @MainActor private func displayImage() { isGrayscale = Preferences.shared.grayscaleImages - let image: UIImage? - switch source { case nil: - image = nil + self.image = nil - case let .imageData(url, data), let .gifData(url, data): + case let .image(url, sourceImage): if isGrayscale { - image = ImageGrayscalifier.convert(url: url, data: data) + ImageGrayscalifier.queue.async { [weak self] in + let grayscale = ImageGrayscalifier.convert(url: url, image: sourceImage) + DispatchQueue.main.async { [weak self] in + self?.image = grayscale + } + } } else { - image = UIImage(data: data) + self.image = sourceImage + } + + case let .gifData(url, _, sourceImage): + if isGrayscale, + let sourceImage { + ImageGrayscalifier.queue.async { [weak self] in + let grayscale = ImageGrayscalifier.convert(url: url, image: sourceImage) + DispatchQueue.main.async { [weak self] in + self?.image = grayscale + } + } + } else { + self.image = sourceImage } case let .cgImage(url, cgImage): if isGrayscale { - image = ImageGrayscalifier.convert(url: url, cgImage: cgImage) + ImageGrayscalifier.queue.async { [weak self] in + let grayscale = ImageGrayscalifier.convert(url: url, cgImage: cgImage) + DispatchQueue.main.async { [weak self] in + self?.image = grayscale + } + } } else { image = UIImage(cgImage: cgImage) } } - - DispatchQueue.main.async { - self.image = image - } } private func createBadgesView(_ badges: Badges) { @@ -409,8 +433,8 @@ class AttachmentView: GIFImageView { fileprivate extension AttachmentView { enum Source { - case imageData(URL, Data) - case gifData(URL, Data) + case image(URL, UIImage) + case gifData(URL, Data, UIImage?) case cgImage(URL, CGImage) }