forked from shadowfacts/Tusker
BaseEmojiLabel improvements
Avoid rechecking disk/memory caches when fetching Use UIImage thumbnail API, rather than UIGraphicsImageRenderer, and make thumbnail off main thread when possible
This commit is contained in:
parent
91ef386a41
commit
8319935a3d
|
@ -37,16 +37,20 @@ class ImageCache {
|
|||
completion?(entry.data, entry.image)
|
||||
return nil
|
||||
} else {
|
||||
return Task.detached(priority: .userInitiated) {
|
||||
let result = await self.fetch(url: url)
|
||||
switch result {
|
||||
case .data(let data):
|
||||
completion?(data, nil)
|
||||
case .dataAndImage(let data, let image):
|
||||
completion?(data, image)
|
||||
case .none:
|
||||
completion?(nil, nil)
|
||||
}
|
||||
return getFromSource(url, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func getFromSource(_ url: URL, completion: ((Data?, UIImage?) -> Void)?) -> Request? {
|
||||
return Task.detached(priority: .userInitiated) {
|
||||
let result = await self.fetch(url: url)
|
||||
switch result {
|
||||
case .data(let data):
|
||||
completion?(data, nil)
|
||||
case .dataAndImage(let data, let image):
|
||||
completion?(data, image)
|
||||
case .none:
|
||||
completion?(nil, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import UIKit
|
|||
|
||||
extension NSTextAttachment {
|
||||
// Based on https://github.com/ReticentJohn/Amaroq/blob/7c5b7088eb9fd1611dcb0f47d43bf8df093e142c/DireFloof/InlineImageHelpers.m
|
||||
@available(iOS, deprecated: 15.0)
|
||||
convenience init(emojiImage image: UIImage, in font: UIFont, with textColor: UIColor = .label) {
|
||||
let adjustedCapHeight = font.capHeight - 1
|
||||
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
||||
|
|
|
@ -40,6 +40,16 @@ extension BaseEmojiLabel {
|
|||
return
|
||||
}
|
||||
|
||||
// Based on https://github.com/ReticentJohn/Amaroq/blob/7c5b7088eb9fd1611dcb0f47d43bf8df093e142c/DireFloof/InlineImageHelpers.m
|
||||
let adjustedCapHeight = emojiFont.capHeight - 1
|
||||
func emojiImageSize(_ image: UIImage) -> CGSize {
|
||||
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
||||
var scale: CGFloat = 1.4
|
||||
scale *= UIScreen.main.scale
|
||||
imageSizeMatchingFontSize = CGSize(width: imageSizeMatchingFontSize.width * scale, height: imageSizeMatchingFontSize.height * scale)
|
||||
return imageSizeMatchingFontSize
|
||||
}
|
||||
|
||||
let emojiImages = MultiThreadDictionary<String, UIImage>()
|
||||
var foundEmojis = false
|
||||
|
||||
|
@ -57,21 +67,35 @@ extension BaseEmojiLabel {
|
|||
foundEmojis = true
|
||||
|
||||
if let image = ImageCache.emojis.get(URL(emoji.url)!)?.image {
|
||||
// if the image is cached, add it immediately
|
||||
emojiImages[emoji.shortcode] = image
|
||||
// if the image is cached, add it immediately.
|
||||
// we generate the thumbnail on the main thread, because it's usually fast enough
|
||||
// and the delay caused by doing it asynchronously looks works.
|
||||
// todo: consider caching these somewhere? the cache needs to take into account both the url and the font size, which may vary across labels, so can't just use ImageCache
|
||||
if let thumbnail = image.preparingThumbnail(of: emojiImageSize(image)),
|
||||
let cgImage = thumbnail.cgImage {
|
||||
// the thumbnail API takes a pixel size and returns an image with scale 1, but we want the actual screen scale, so convert
|
||||
// see FB12187798
|
||||
emojiImages[emoji.shortcode] = UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
|
||||
}
|
||||
} else {
|
||||
// otherwise, perform the network request
|
||||
|
||||
group.enter()
|
||||
// todo: ImageCache.emojis.get here will re-check the memory and disk caches, there should be another method to force-refetch
|
||||
let request = ImageCache.emojis.get(URL(emoji.url)!) { (_, image) in
|
||||
guard let image = image,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: image) else {
|
||||
group.leave()
|
||||
return
|
||||
let request = ImageCache.emojis.getFromSource(URL(emoji.url)!) { (_, image) in
|
||||
guard let image else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
image.prepareThumbnail(of: emojiImageSize(image)) { thumbnail in
|
||||
guard let thumbnail = thumbnail?.cgImage,
|
||||
case let rescaled = UIImage(cgImage: thumbnail, scale: UIScreen.main.scale, orientation: .up),
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: rescaled) else {
|
||||
group.leave()
|
||||
return
|
||||
}
|
||||
emojiImages[emoji.shortcode] = transformedImage
|
||||
group.leave()
|
||||
}
|
||||
emojiImages[emoji.shortcode] = transformedImage
|
||||
group.leave()
|
||||
}
|
||||
if let request = request {
|
||||
emojiRequests.append(request)
|
||||
|
@ -91,10 +115,8 @@ extension BaseEmojiLabel {
|
|||
// even though the closures is invoked on the same thread that withLock is called, so it's unclear why it needs to be @Sendable (FB11494878)
|
||||
// so, just ignore the warnings
|
||||
let emojiAttachments = emojiImages.withLock {
|
||||
let emojiFont = self.emojiFont
|
||||
let emojiTextColor = self.emojiTextColor
|
||||
return $0.mapValues { image in
|
||||
NSTextAttachment(emojiImage: image, in: emojiFont, with: emojiTextColor)
|
||||
NSTextAttachment(image: image)
|
||||
}
|
||||
}
|
||||
let placeholder = usePlaceholders ? NSTextAttachment(emojiPlaceholderIn: self.emojiFont) : nil
|
||||
|
|
Loading…
Reference in New Issue