mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
155 lines
5.7 KiB
Swift
155 lines
5.7 KiB
Swift
import Foundation
|
|
|
|
private final class ImageCacheData {
|
|
let size: CGSize
|
|
let bytesPerRow: Int
|
|
var data: NSPurgeableData
|
|
|
|
var isDiscarded: Bool {
|
|
return self.data.isContentDiscarded()
|
|
}
|
|
|
|
var image: UIImage? {
|
|
if self.data.beginContentAccess() {
|
|
return self.createImage()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
init(size: CGSize, generator: CGContextRef -> Void, @noescape takenImage: UIImage -> Void) {
|
|
self.size = size
|
|
|
|
self.bytesPerRow = (4 * Int(size.width) + 15) & (~15)
|
|
self.data = NSPurgeableData(length: self.bytesPerRow * Int(size.height))!
|
|
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue
|
|
|
|
if let context = CGBitmapContextCreate(self.data.mutableBytes, Int(size.width), Int(size.height), 8, bytesPerRow, colorSpace, bitmapInfo)
|
|
{
|
|
CGContextTranslateCTM(context, size.width / 2.0, size.height / 2.0)
|
|
CGContextScaleCTM(context, 1.0, -1.0)
|
|
CGContextTranslateCTM(context, -size.width / 2.0, -size.height / 2.0)
|
|
|
|
UIGraphicsPushContext(context)
|
|
|
|
generator(context)
|
|
|
|
UIGraphicsPopContext()
|
|
}
|
|
|
|
takenImage(self.createImage())
|
|
}
|
|
|
|
private func createImage() -> UIImage {
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue
|
|
|
|
let unmanagedData = withUnsafePointer(&self.data, { pointer in
|
|
return Unmanaged<NSPurgeableData>.fromOpaque(COpaquePointer(pointer))
|
|
})
|
|
unmanagedData.retain()
|
|
let dataProvider = CGDataProviderCreateWithData(UnsafeMutablePointer<Void>(unmanagedData.toOpaque()), self.data.bytes, self.bytesPerRow, { info, _, _ in
|
|
let unmanagedData = Unmanaged<NSPurgeableData>.fromOpaque(COpaquePointer(info))
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
|
|
unmanagedData.takeUnretainedValue().endContentAccess()
|
|
unmanagedData.release()
|
|
})
|
|
})
|
|
|
|
let image = CGImageCreate(Int(self.size.width), Int(self.size.height), 8, 32, self.bytesPerRow, colorSpace, CGBitmapInfo(rawValue: bitmapInfo), dataProvider, nil, false, CGColorRenderingIntent(rawValue: 0)!)
|
|
|
|
let result = UIImage(CGImage: image!)
|
|
return result
|
|
}
|
|
}
|
|
|
|
private final class ImageCacheResidentImage {
|
|
let key: String
|
|
let image: UIImage
|
|
var accessIndex: Int
|
|
|
|
init(key: String, image: UIImage, accessIndex: Int) {
|
|
self.key = key
|
|
self.image = image
|
|
self.accessIndex = accessIndex
|
|
}
|
|
}
|
|
|
|
public final class ImageCache {
|
|
let maxResidentSize: Int
|
|
var mutex = pthread_mutex_t()
|
|
|
|
private var imageDatas: [String : ImageCacheData] = [:]
|
|
private var residentImages: [String : ImageCacheResidentImage] = [:]
|
|
var nextAccessIndex = 1
|
|
var residentImagesSize = 0
|
|
|
|
public init(maxResidentSize: Int) {
|
|
self.maxResidentSize = maxResidentSize
|
|
pthread_mutex_init(&self.mutex, nil)
|
|
}
|
|
|
|
deinit {
|
|
pthread_mutex_destroy(&self.mutex)
|
|
}
|
|
|
|
public func addImageForKey(key: String, size: CGSize, generator: CGContextRef -> Void) {
|
|
var image: UIImage?
|
|
let imageData = ImageCacheData(size: size, generator: generator, takenImage: { image = $0 })
|
|
|
|
pthread_mutex_lock(&self.mutex)
|
|
self.imageDatas[key] = imageData
|
|
self.addResidentImage(image!, forKey: key)
|
|
pthread_mutex_unlock(&self.mutex)
|
|
}
|
|
|
|
public func imageForKey(key: String) -> UIImage? {
|
|
var image: UIImage?
|
|
|
|
pthread_mutex_lock(&self.mutex);
|
|
if let residentImage = self.residentImages[key] {
|
|
image = residentImage.image
|
|
self.nextAccessIndex++
|
|
residentImage.accessIndex = self.nextAccessIndex
|
|
} else {
|
|
if let imageData = self.imageDatas[key] {
|
|
if let takenImage = imageData.image {
|
|
image = takenImage
|
|
self.addResidentImage(takenImage, forKey: key)
|
|
} else {
|
|
self.imageDatas.removeValueForKey(key)
|
|
}
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&self.mutex)
|
|
|
|
return image
|
|
}
|
|
|
|
private func addResidentImage(image: UIImage, forKey key: String) {
|
|
let imageSize = Int(image.size.width * image.size.height * image.scale) * 4
|
|
|
|
if self.residentImagesSize + imageSize > self.maxResidentSize {
|
|
let sizeToRemove = self.residentImagesSize - (self.maxResidentSize - imageSize)
|
|
let sortedImages = self.residentImages.values.sort({ $0.accessIndex < $1.accessIndex })
|
|
|
|
var removedSize = 0
|
|
var i = sortedImages.count - 1
|
|
while i >= 0 && removedSize < sizeToRemove {
|
|
let currentImage = sortedImages[i]
|
|
let currentImageSize = Int(currentImage.image.size.width * currentImage.image.size.height * currentImage.image.scale) * 4
|
|
removedSize += currentImageSize
|
|
self.residentImages.removeValueForKey(currentImage.key)
|
|
i--
|
|
}
|
|
|
|
self.residentImagesSize = max(0, self.residentImagesSize - removedSize)
|
|
}
|
|
|
|
self.residentImagesSize += imageSize
|
|
self.nextAccessIndex++
|
|
self.residentImages[key] = ImageCacheResidentImage(key: key, image: image, accessIndex: self.nextAccessIndex)
|
|
}
|
|
}
|