import Foundation import Postbox import SwiftSignalKit import Display import AVFoundation import ImageIO import TelegramCorePrivateModule func largestRepresentationForPhoto(_ photo: TelegramMediaImage) -> TelegramMediaImageRepresentation? { return photo.representationForDisplayAtSize(CGSize(width: 1280.0, height: 1280.0)) } private func chatMessagePhotoDatas(account: Account, photo: TelegramMediaImage) -> Signal<(Data?, Data?, Int), NoError> { if let smallestRepresentation = smallestImageRepresentation(photo.representations), let largestRepresentation = largestRepresentationForPhoto(photo), let smallestSize = smallestRepresentation.size, let largestSize = largestRepresentation.size { let thumbnailResource = CloudFileMediaResource(location: smallestRepresentation.location, size: smallestSize) let fullSizeResource = CloudFileMediaResource(location: largestRepresentation.location, size: largestSize) let maybeFullSize = account.postbox.mediaBox.resourceData(fullSizeResource) let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, Data?, Int), NoError> in if maybeData.size >= fullSizeResource.size { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single((nil, loadedData, fullSizeResource.size)) } else { let fetchedThumbnail = account.postbox.mediaBox.fetchedResource(thumbnailResource) let thumbnail = Signal { subscriber in let fetchedDisposable = fetchedThumbnail.start() let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource).start(next: { next in subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { fetchedDisposable.dispose() thumbnailDisposable.dispose() } } let fullSizeData = account.postbox.mediaBox.resourceData(fullSizeResource) |> map { next in return next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe) } return thumbnail |> mapToSignal { thumbnailData in return fullSizeData |> map { fullSizeData in return (thumbnailData, fullSizeData, fullSizeResource.size) } } } } |> filter({ $0.0 != nil || $0.1 != nil }) return signal } else { return .never() } } private func chatMessageFileDatas(account: Account, file: TelegramMediaFile, progressive: Bool = false) -> Signal<(Data?, (Data, String)?, Int), NoError> { if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let smallestSize = smallestRepresentation.size { let thumbnailResource = CloudFileMediaResource(location: smallestRepresentation.location, size: smallestSize) let fullSizeResource = CloudFileMediaResource(location: file.location, size: file.size) let maybeFullSize = account.postbox.mediaBox.resourceData(fullSizeResource) let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, (Data, String)?, Int), NoError> in if maybeData.size >= fullSizeResource.size { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single((nil, loadedData == nil ? nil : (loadedData!, maybeData.path), fullSizeResource.size)) } else { let fetchedThumbnail = account.postbox.mediaBox.fetchedResource(thumbnailResource) let thumbnail = Signal { subscriber in let fetchedDisposable = fetchedThumbnail.start() let thumbnailDisposable = account.postbox.mediaBox.resourceData(thumbnailResource).start(next: { next in subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { fetchedDisposable.dispose() thumbnailDisposable.dispose() } } let fullSizeDataAndPath = account.postbox.mediaBox.resourceData(fullSizeResource, complete: !progressive) |> map { next -> (Data, String)? in let data = next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe) return data == nil ? nil : (data!, next.path) } return thumbnail |> mapToSignal { thumbnailData in return fullSizeDataAndPath |> map { dataAndPath in return (thumbnailData, dataAndPath, fullSizeResource.size) } } } } |> filter({ $0.0 != nil || $0.1 != nil }) return signal } else { return .never() } } private enum Corner: Hashable { case TopLeft(Int), TopRight(Int), BottomLeft(Int), BottomRight(Int) var hashValue: Int { switch self { case let .TopLeft(radius): return radius | (1 << 24) case let .TopRight(radius): return radius | (2 << 24) case let .BottomLeft(radius): return radius | (3 << 24) case let .BottomRight(radius): return radius | (2 << 24) } } var radius: Int { switch self { case let .TopLeft(radius): return radius case let .TopRight(radius): return radius case let .BottomLeft(radius): return radius case let .BottomRight(radius): return radius } } } private func ==(lhs: Corner, rhs: Corner) -> Bool { switch lhs { case let .TopLeft(lhsRadius): switch rhs { case let .TopLeft(rhsRadius) where rhsRadius == lhsRadius: return true default: return false } case let .TopRight(lhsRadius): switch rhs { case let .TopRight(rhsRadius) where rhsRadius == lhsRadius: return true default: return false } case let .BottomLeft(lhsRadius): switch rhs { case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius: return true default: return false } case let .BottomRight(lhsRadius): switch rhs { case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius: return true default: return false } } } private enum Tail: Hashable { case BottomLeft(Int) case BottomRight(Int) var hashValue: Int { switch self { case let .BottomLeft(radius): return radius | (1 << 24) case let .BottomRight(radius): return radius | (2 << 24) } } var radius: Int { switch self { case let .BottomLeft(radius): return radius case let .BottomRight(radius): return radius } } } private func ==(lhs: Tail, rhs: Tail) -> Bool { switch lhs { case let .BottomLeft(lhsRadius): switch rhs { case let .BottomLeft(rhsRadius) where rhsRadius == lhsRadius: return true default: return false } case let .BottomRight(lhsRadius): switch rhs { case let .BottomRight(rhsRadius) where rhsRadius == lhsRadius: return true default: return false } } } private var cachedCorners: [Corner: DrawingContext] = [:] private let cachedCornersLock = SwiftSignalKit.Lock() private var cachedTails: [Tail: DrawingContext] = [:] private let cachedTailsLock = SwiftSignalKit.Lock() private func cornerContext(_ corner: Corner) -> DrawingContext { var cached: DrawingContext? cachedCornersLock.locked { cached = cachedCorners[corner] } if let cached = cached { return cached } else { let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true) context.withContext { c in c.setBlendMode(.copy) c.setFillColor(UIColor.black.cgColor) let rect: CGRect switch corner { case let .TopLeft(radius): rect = CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) case let .TopRight(radius): rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: 0.0), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) case let .BottomLeft(radius): rect = CGRect(origin: CGPoint(x: 0.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) case let .BottomRight(radius): rect = CGRect(origin: CGPoint(x: -CGFloat(radius), y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) } c.fillEllipse(in: rect) } cachedCornersLock.locked { cachedCorners[corner] = context } return context } } private func tailContext(_ tail: Tail) -> DrawingContext { var cached: DrawingContext? cachedTailsLock.locked { cached = cachedTails[tail] } if let cached = cached { return cached } else { let context = DrawingContext(size: CGSize(width: CGFloat(tail.radius) + 3.0, height: CGFloat(tail.radius)), clear: true) context.withContext { c in c.setBlendMode(.copy) c.setFillColor(UIColor.black.cgColor) let rect: CGRect switch tail { case let .BottomLeft(radius): rect = CGRect(origin: CGPoint(x: 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) c.moveTo(x: 3.0, y: 0.0) c.addLineTo(x: 3.0, y: 8.7) c.addLineTo(x: 2.0, y: 11.7) c.addLineTo(x: 1.5, y: 12.7) c.addLineTo(x: 0.8, y: 13.7) c.addLineTo(x: 0.2, y: 14.4) c.addLineTo(x: 3.5, y: 13.8) c.addLineTo(x: 5.0, y: 13.2) c.addLineTo(x: 3.0 + CGFloat(radius) - 9.5, y: 11.5) c.closePath() c.fillPath() case let .BottomRight(radius): rect = CGRect(origin: CGPoint(x: -CGFloat(radius) + 3.0, y: -CGFloat(radius)), size: CGSize(width: CGFloat(radius << 1), height: CGFloat(radius << 1))) /*CGContextMoveToPoint(c, 3.0, 0.0) CGContextAddLineToPoint(c, 3.0, 8.7) CGContextAddLineToPoint(c, 2.0, 11.7) CGContextAddLineToPoint(c, 1.5, 12.7) CGContextAddLineToPoint(c, 0.8, 13.7) CGContextAddLineToPoint(c, 0.2, 14.4) CGContextAddLineToPoint(c, 3.5, 13.8) CGContextAddLineToPoint(c, 5.0, 13.2) CGContextAddLineToPoint(c, 3.0 + CGFloat(radius) - 9.5, 11.5) CGContextClosePath(c) CGContextFillPath(c)*/ } c.fillEllipse(in: rect) } cachedCornersLock.locked { cachedTails[tail] = context } return context } } private func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) { let corners = arguments.corners let drawingRect = arguments.drawingRect if case let .Corner(radius) = corners.topLeft, radius > CGFloat(FLT_EPSILON) { let corner = cornerContext(.TopLeft(Int(radius))) context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.minY)) } if case let .Corner(radius) = corners.topRight, radius > CGFloat(FLT_EPSILON) { let corner = cornerContext(.TopRight(Int(radius))) context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.minY)) } switch corners.bottomLeft { case let .Corner(radius): if radius > CGFloat(FLT_EPSILON) { let corner = cornerContext(.BottomLeft(Int(radius))) context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius)) } case let .Tail(radius): if radius > CGFloat(FLT_EPSILON) { let tail = tailContext(.BottomLeft(Int(radius))) let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0)) context.withContext { c in c.setFillColor(color.cgColor) c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0)) } context.blt(tail, at: CGPoint(x: drawingRect.minX - 3.0, y: drawingRect.maxY - radius)) } } switch corners.bottomRight { case let .Corner(radius): if radius > CGFloat(FLT_EPSILON) { let corner = cornerContext(.BottomRight(Int(radius))) context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius)) } case let .Tail(radius): if radius > CGFloat(FLT_EPSILON) { let tail = tailContext(.BottomRight(Int(radius))) context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius - 3.0, y: drawingRect.maxY - radius)) } } } func chatMessagePhoto(account: Account, photo: TelegramMediaImage) -> Signal<(TransformImageArguments) -> DrawingContext, NoError> { let signal = chatMessagePhotoDatas(account: account, photo: photo) return signal |> map { (thumbnailData, fullSizeData, fullTotalSize) in return { arguments in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) var fullSizeImage: CGImage? if let fullSizeData = fullSizeData { if fullSizeData.count >= fullTotalSize { let options: [NSString: NSObject] = [ kCGImageSourceThumbnailMaxPixelSize: max(fittedSize.width * context.scale, fittedSize.height * context.scale), kCGImageSourceCreateThumbnailFromImageAlways: true ] if let imageSource = CGImageSourceCreateWithData(fullSizeData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { fullSizeImage = image } } else { let imageSource = CGImageSourceCreateIncremental(nil) CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeData.count >= fullTotalSize) let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image } } } var thumbnailImage: CGImage? if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { thumbnailImage = image } var blurredThumbnailImage: UIImage? if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(in: CGRect(origin: CGPoint(), size: thumbnailContextSize), image: thumbnailImage) } telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.boundingSize != arguments.imageSize { c.fill(arguments.drawingRect) } c.setBlendMode(.copy) if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage { c.interpolationQuality = .low c.draw(in: fittedRect, image: cgImage) } if let fullSizeImage = fullSizeImage { c.setBlendMode(.normal) c.interpolationQuality = .medium c.draw(in: fittedRect, image: fullSizeImage) } } addCorners(context, arguments: arguments) return context } } } func chatMessagePhotoStatus(account: Account, photo: TelegramMediaImage) -> Signal { if let largestRepresentation = largestRepresentationForPhoto(photo), let largestSize = largestRepresentation.size { let fullSizeResource = CloudFileMediaResource(location: largestRepresentation.location, size: largestSize) return account.postbox.mediaBox.resourceStatus(fullSizeResource) } else { return .never() } } func chatMessagePhotoInteractiveFetched(account: Account, photo: TelegramMediaImage) -> Signal { if let largestRepresentation = largestRepresentationForPhoto(photo), let largestSize = largestRepresentation.size { let fullSizeResource = CloudFileMediaResource(location: largestRepresentation.location, size: largestSize) return account.postbox.mediaBox.fetchedResource(fullSizeResource) } else { return .never() } } func chatMessagePhotoCancelInteractiveFetch(account: Account, photo: TelegramMediaImage) { if let largestRepresentation = largestRepresentationForPhoto(photo), let largestSize = largestRepresentation.size { let fullSizeResource = CloudFileMediaResource(location: largestRepresentation.location, size: largestSize) return account.postbox.mediaBox.cancelInteractiveResourceFetch(fullSizeResource) } } func chatWebpageSnippetPhotoData(account: Account, photo: TelegramMediaImage) -> Signal { if let closestRepresentation = photo.representationForDisplayAtSize(CGSize(width: 120.0, height: 120.0)) { let resource = CloudFileMediaResource(location: closestRepresentation.location, size: closestRepresentation.size ?? 0) let resourceData = account.postbox.mediaBox.resourceData(resource) |> map { next in return next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe) } return Signal { subscriber in let disposable = DisposableSet() disposable.add(resourceData.start(next: { data in subscriber.putNext(data) }, error: { error in subscriber.putError(error) }, completed: { subscriber.putCompletion() })) disposable.add(account.postbox.mediaBox.fetchedResource(resource).start()) return disposable } } else { return .never() } } func chatWebpageSnippetPhoto(account: Account, photo: TelegramMediaImage) -> Signal<(TransformImageArguments) -> DrawingContext, NoError> { let signal = chatWebpageSnippetPhotoData(account: account, photo: photo) return signal |> map { fullSizeData in return { arguments in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) var fullSizeImage: CGImage? if let fullSizeData = fullSizeData { let options: [NSString: NSObject] = [ kCGImageSourceThumbnailMaxPixelSize: max(fittedSize.width * context.scale, fittedSize.height * context.scale), kCGImageSourceCreateThumbnailFromImageAlways: true ] if let imageSource = CGImageSourceCreateWithData(fullSizeData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { fullSizeImage = image } } context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.boundingSize.width > arguments.imageSize.width || arguments.boundingSize.height > arguments.imageSize.height { c.fill(arguments.drawingRect) } if let fullSizeImage = fullSizeImage { c.interpolationQuality = .medium c.draw(in: fittedRect, image: fullSizeImage) } } addCorners(context, arguments: arguments) return context } } } func chatMessageVideo(account: Account, video: TelegramMediaFile) -> Signal<(TransformImageArguments) -> DrawingContext, NoError> { let signal = chatMessageFileDatas(account: account, file: video) return signal |> map { (thumbnailData, fullSizeDataAndPath, fullTotalSize) in return { arguments in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) if arguments.drawingSize.width.isLessThanOrEqualTo(0.0) || arguments.drawingSize.height.isLessThanOrEqualTo(0.0) { return context } let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) var fullSizeImage: CGImage? if let fullSizeDataAndPath = fullSizeDataAndPath { if fullSizeDataAndPath.0.count >= fullTotalSize { if video.mimeType.hasPrefix("video/") { let tempFilePath = NSTemporaryDirectory() + "\(arc4random()).mov" _ = try? FileManager.default.removeItem(atPath: tempFilePath) _ = try? FileManager.default.linkItem(atPath: fullSizeDataAndPath.1, toPath: tempFilePath) let asset = AVAsset(url: URL(fileURLWithPath: tempFilePath)) let imageGenerator = AVAssetImageGenerator(asset: asset) imageGenerator.maximumSize = CGSize(width: 800.0, height: 800.0) imageGenerator.appliesPreferredTrackTransform = true if let image = try? imageGenerator.copyCGImage(at: CMTime(seconds: 0.0, preferredTimescale: asset.duration.timescale), actualTime: nil) { fullSizeImage = image } } /*let options: [NSString: NSObject] = [ kCGImageSourceThumbnailMaxPixelSize: max(fittedSize.width * context.scale, fittedSize.height * context.scale), kCGImageSourceCreateThumbnailFromImageAlways: true ] if let imageSource = CGImageSourceCreateWithData(fullSizeData, nil), image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { fullSizeImage = image }*/ } else { /*let imageSource = CGImageSourceCreateIncremental(nil) CGImageSourceUpdateData(imageSource, fullSizeData as CFDataRef, fullSizeData.length >= fullTotalSize) var options: [NSString : NSObject!] = [:] options[kCGImageSourceShouldCache as NSString] = false as NSNumber if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionaryRef) { fullSizeImage = image }*/ } } var thumbnailImage: CGImage? if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { thumbnailImage = image } var blurredThumbnailImage: UIImage? if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(in: CGRect(origin: CGPoint(), size: thumbnailContextSize), image: thumbnailImage) } telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.boundingSize != arguments.imageSize { c.fill(arguments.drawingRect) } c.setBlendMode(.copy) if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage { c.interpolationQuality = .low c.draw(in: fittedRect, image: cgImage) } if let fullSizeImage = fullSizeImage { c.setBlendMode(.normal) c.interpolationQuality = .medium c.draw(in: fittedRect, image: fullSizeImage) } } addCorners(context, arguments: arguments) return context } } } func chatMessageImageFile(account: Account, file: TelegramMediaFile, progressive: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext, NoError> { let signal = chatMessageFileDatas(account: account, file: file, progressive: progressive) return signal |> map { (thumbnailData, fullSizeDataAndPath, fullTotalSize) in return { arguments in assertNotOnMainThread() let context = DrawingContext(size: arguments.drawingSize, clear: true) let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) var fullSizeImage: CGImage? if let fullSizeDataAndPath = fullSizeDataAndPath { if fullSizeDataAndPath.0.count >= fullTotalSize { let options: [NSString: NSObject] = [ kCGImageSourceThumbnailMaxPixelSize: max(fittedSize.width * context.scale, fittedSize.height * context.scale), kCGImageSourceCreateThumbnailFromImageAlways: true ] if let imageSource = CGImageSourceCreateWithData(fullSizeDataAndPath.0, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) { fullSizeImage = image } } else if progressive { let imageSource = CGImageSourceCreateIncremental(nil) CGImageSourceUpdateData(imageSource, fullSizeDataAndPath.0 as CFData, fullSizeDataAndPath.0.count >= fullTotalSize) let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image } } } var thumbnailImage: CGImage? if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { thumbnailImage = image } var blurredThumbnailImage: UIImage? if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(in: CGRect(origin: CGPoint(), size: thumbnailContextSize), image: thumbnailImage) } telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.boundingSize != arguments.imageSize { c.fill(arguments.drawingRect) } c.setBlendMode(.copy) if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage { c.interpolationQuality = .low c.draw(in: fittedRect, image: cgImage) } if let fullSizeImage = fullSizeImage { c.setBlendMode(.normal) c.interpolationQuality = .medium c.draw(in: fittedRect, image: fullSizeImage) } } addCorners(context, arguments: arguments) return context } } } func chatMessageFileStatus(account: Account, file: TelegramMediaFile) -> Signal { let fullSizeResource = CloudFileMediaResource(location: file.location, size: file.size) return account.postbox.mediaBox.resourceStatus(fullSizeResource) } func chatMessageFileInteractiveFetched(account: Account, file: TelegramMediaFile) -> Signal { let fullSizeResource = CloudFileMediaResource(location: file.location, size: file.size) return account.postbox.mediaBox.fetchedResource(fullSizeResource) } func chatMessageFileCancelInteractiveFetch(account: Account, file: TelegramMediaFile) { let fullSizeResource = CloudFileMediaResource(location: file.location, size: file.size) return account.postbox.mediaBox.cancelInteractiveResourceFetch(fullSizeResource) }