mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
232 lines
9.3 KiB
Swift
232 lines
9.3 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Photos
|
|
import Postbox
|
|
import SwiftSignalKit
|
|
import ImageCompression
|
|
import Accelerate.vImage
|
|
|
|
private final class RequestId {
|
|
var id: PHImageRequestID?
|
|
var invalidated: Bool = false
|
|
}
|
|
|
|
private func resizedImage(_ image: UIImage, for size: CGSize) -> UIImage? {
|
|
guard let cgImage = image.cgImage else {
|
|
return nil
|
|
}
|
|
|
|
var format = vImage_CGImageFormat(bitsPerComponent: 8,
|
|
bitsPerPixel: 32,
|
|
colorSpace: nil,
|
|
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
|
|
version: 0,
|
|
decode: nil,
|
|
renderingIntent: .defaultIntent)
|
|
|
|
var error: vImage_Error
|
|
var sourceBuffer = vImage_Buffer()
|
|
defer { sourceBuffer.data.deallocate() }
|
|
error = vImageBuffer_InitWithCGImage(&sourceBuffer,
|
|
&format,
|
|
nil,
|
|
cgImage,
|
|
vImage_Flags(kvImageNoFlags))
|
|
guard error == kvImageNoError else { return nil }
|
|
|
|
var destinationBuffer = vImage_Buffer()
|
|
error = vImageBuffer_Init(&destinationBuffer,
|
|
vImagePixelCount(size.height),
|
|
vImagePixelCount(size.width),
|
|
format.bitsPerPixel,
|
|
vImage_Flags(kvImageNoFlags))
|
|
guard error == kvImageNoError else {
|
|
return nil
|
|
}
|
|
|
|
error = vImageScale_ARGB8888(&sourceBuffer,
|
|
&destinationBuffer,
|
|
nil,
|
|
vImage_Flags(kvImageHighQualityResampling))
|
|
guard error == kvImageNoError else {
|
|
return nil
|
|
}
|
|
|
|
guard let resizedImage =
|
|
vImageCreateCGImageFromBuffer(&destinationBuffer,
|
|
&format,
|
|
nil,
|
|
nil,
|
|
vImage_Flags(kvImageNoAllocate),
|
|
&error)?.takeRetainedValue(),
|
|
error == kvImageNoError
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return UIImage(cgImage: resizedImage)
|
|
}
|
|
|
|
extension UIImage.Orientation {
|
|
init(_ cgOrientation: CGImagePropertyOrientation) {
|
|
switch cgOrientation {
|
|
case .up: self = .up
|
|
case .upMirrored: self = .upMirrored
|
|
case .down: self = .down
|
|
case .downMirrored: self = .downMirrored
|
|
case .left: self = .left
|
|
case .leftMirrored: self = .leftMirrored
|
|
case .right: self = .right
|
|
case .rightMirrored: self = .rightMirrored
|
|
}
|
|
}
|
|
}
|
|
|
|
private let fetchPhotoWorkers = ThreadPool(threadCount: 3, threadPriority: 0.2)
|
|
|
|
public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
|
return Signal { subscriber in
|
|
let queue = ThreadPoolQueue(threadPool: fetchPhotoWorkers)
|
|
|
|
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
|
|
let requestId = Atomic<RequestId>(value: RequestId())
|
|
if fetchResult.count != 0 {
|
|
let asset = fetchResult.object(at: 0)
|
|
let option = PHImageRequestOptions()
|
|
option.deliveryMode = .highQualityFormat
|
|
option.isNetworkAccessAllowed = true
|
|
option.isSynchronous = false
|
|
|
|
let size = CGSize(width: 1280.0, height: 1280.0)
|
|
|
|
queue.addTask(ThreadPoolTask({ _ in
|
|
let startTime = CACurrentMediaTime()
|
|
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
let requestIdValue = PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: option, resultHandler: { (image, info) -> Void in
|
|
Queue.concurrentDefaultQueue().async {
|
|
requestId.with { current -> Void in
|
|
if !current.invalidated {
|
|
current.id = nil
|
|
current.invalidated = true
|
|
}
|
|
}
|
|
if let image = image {
|
|
if let info = info, let degraded = info[PHImageResultIsDegradedKey], (degraded as AnyObject).boolValue!{
|
|
|
|
} else {
|
|
#if DEBUG
|
|
print("load completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
|
#endif
|
|
|
|
let scale = min(1.0, min(size.width / max(1.0, image.size.width), size.height / max(1.0, image.size.height)))
|
|
let scaledSize = CGSize(width: floor(image.size.width * scale), height: floor(image.size.height * scale))
|
|
let scaledImage = resizedImage(image, for: scaledSize)
|
|
|
|
#if DEBUG
|
|
print("scaled completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
|
#endif
|
|
|
|
if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6) {
|
|
#if DEBUG
|
|
print("compression completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
|
#endif
|
|
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< Int64(data.count), complete: true))
|
|
subscriber.putCompletion()
|
|
} else {
|
|
subscriber.putCompletion()
|
|
}
|
|
semaphore.signal()
|
|
}
|
|
} else {
|
|
semaphore.signal()
|
|
}
|
|
}
|
|
})
|
|
requestId.with { current -> Void in
|
|
if !current.invalidated {
|
|
current.id = requestIdValue
|
|
}
|
|
}
|
|
semaphore.wait()
|
|
}))
|
|
} else {
|
|
subscriber.putNext(.reset)
|
|
}
|
|
|
|
return ActionDisposable {
|
|
let requestIdValue = requestId.with { current -> PHImageRequestID? in
|
|
if !current.invalidated {
|
|
let value = current.id
|
|
current.id = nil
|
|
current.invalidated = true
|
|
return value
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
if let requestIdValue = requestIdValue {
|
|
PHImageManager.default().cancelImageRequest(requestIdValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func fetchPhotoLibraryImage(localIdentifier: String, thumbnail: Bool) -> Signal<(UIImage, Bool)?, NoError> {
|
|
return Signal { subscriber in
|
|
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
|
|
let requestId = Atomic<RequestId>(value: RequestId())
|
|
if fetchResult.count != 0 {
|
|
let asset = fetchResult.object(at: 0)
|
|
let option = PHImageRequestOptions()
|
|
option.deliveryMode = .highQualityFormat
|
|
if thumbnail {
|
|
option.resizeMode = .fast
|
|
}
|
|
option.isNetworkAccessAllowed = true
|
|
option.isSynchronous = false
|
|
|
|
let targetSize: CGSize = thumbnail ? CGSize(width: 128.0, height: 128.0) : PHImageManagerMaximumSize
|
|
let requestIdValue = PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: option, resultHandler: { (image, info) -> Void in
|
|
Queue.concurrentDefaultQueue().async {
|
|
requestId.with { current -> Void in
|
|
if !current.invalidated {
|
|
current.id = nil
|
|
current.invalidated = true
|
|
}
|
|
}
|
|
if let image = image {
|
|
subscriber.putNext((image, thumbnail))
|
|
subscriber.putCompletion()
|
|
}
|
|
}
|
|
})
|
|
requestId.with { current -> Void in
|
|
if !current.invalidated {
|
|
current.id = requestIdValue
|
|
}
|
|
}
|
|
} else {
|
|
subscriber.putNext(nil)
|
|
subscriber.putCompletion()
|
|
}
|
|
|
|
return ActionDisposable {
|
|
let requestIdValue = requestId.with { current -> PHImageRequestID? in
|
|
if !current.invalidated {
|
|
let value = current.id
|
|
current.id = nil
|
|
current.invalidated = true
|
|
return value
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
if let requestIdValue = requestIdValue {
|
|
PHImageManager.default().cancelImageRequest(requestIdValue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|