import Foundation import UIKit import SwiftSignalKit import Display import Postbox import TelegramCore import TelegramUIPrivateModule private func wallpaperDatas(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) { let maybeFullSize: Signal if thumbnail, let file = fileReference?.media { maybeFullSize = combineLatest(accountManager.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: false, attemptSynchronously: synchronousLoad), account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: false, attemptSynchronously: synchronousLoad)) |> mapToSignal { maybeSharedData, maybeData -> Signal in if maybeSharedData.complete { return .single(maybeSharedData) } else if maybeData.complete { return .single(maybeData) } else { return combineLatest(accountManager.mediaBox.resourceData(file.resource), account.postbox.mediaBox.resourceData(file.resource)) |> mapToSignal { maybeSharedData, maybeData -> Signal in if maybeSharedData.complete { return accountManager.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: true) } else if maybeData.complete { return account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: true) } else { return .single(maybeData) } } } } } else { if thumbnail { maybeFullSize = combineLatest(accountManager.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: false), account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: false)) |> mapToSignal { maybeSharedData, maybeData -> Signal in if maybeSharedData.complete { return .single(maybeSharedData) } else if maybeData.complete { return .single(maybeData) } else { return account.postbox.mediaBox.resourceData(largestRepresentation.resource) } } } else { maybeFullSize = combineLatest(accountManager.mediaBox.resourceData(largestRepresentation.resource), account.postbox.mediaBox.resourceData(largestRepresentation.resource)) |> map { sharedData, data -> MediaResourceData in if sharedData.complete { return sharedData } else { return data } } } } let decodedThumbnailData = fileReference?.media.immediateThumbnailData.flatMap(decodeTinyThumbnail) let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) if alwaysShowThumbnailFirst, let decodedThumbnailData = decodedThumbnailData { return .single((decodedThumbnailData, nil, false)) |> then(.complete() |> delay(0.05, queue: Queue.concurrentDefaultQueue())) |> then(.single((nil, loadedData, true))) } else { return .single((nil, loadedData, true)) } } else { let fetchedThumbnail: Signal if let _ = decodedThumbnailData { fetchedThumbnail = .complete() } else { fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: representations[smallestIndex].reference) } let fetchedFullSize = fetchedMediaResource(postbox: account.postbox, reference: representations[largestIndex].reference) let thumbnailData: Signal if let decodedThumbnailData = decodedThumbnailData { thumbnailData = .single(decodedThumbnailData) } else { thumbnailData = Signal { subscriber in let fetchedDisposable = fetchedThumbnail.start() let thumbnailDisposable = account.postbox.mediaBox.resourceData(smallestRepresentation.resource).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() } } } var fullSizeData: Signal<(Data?, Bool), NoError> if autoFetchFullSize { fullSizeData = Signal<(Data?, Bool), NoError> { subscriber in let fetchedFullSizeDisposable = fetchedFullSize.start() let fullSizeDisposable = account.postbox.mediaBox.resourceData(largestRepresentation.resource).start(next: { next in subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { fetchedFullSizeDisposable.dispose() fullSizeDisposable.dispose() } } } else { fullSizeData = account.postbox.mediaBox.resourceData(largestRepresentation.resource) |> map { next -> (Data?, Bool) in return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } } if thumbnail, let file = fileReference?.media { let betterThumbnailData = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: false, fetch: true) |> map { next -> (Data?, Bool) in return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete) } |> filter { (data, _) -> Bool in return data != nil } return thumbnailData |> mapToSignal { thumbnailData in return fullSizeData |> mapToSignal { (fullSizeData, complete) in if complete { return .single((thumbnailData, fullSizeData, complete)) |> then( betterThumbnailData |> map { (betterFullSizeData, complete) in return (thumbnailData, betterFullSizeData ?? fullSizeData, complete) }) } else { return .single((thumbnailData, fullSizeData, complete)) } } } } else { return thumbnailData |> mapToSignal { thumbnailData in return fullSizeData |> map { (fullSizeData, complete) in return (thumbnailData, fullSizeData, complete) } } } } } |> filter({ $0.0 != nil || $0.1 != nil }) return signal } else { return .never() } } func wallpaperImage(account: Account, accountManager: AccountManager, fileReference: FileMediaReference? = nil, representations: [ImageRepresentationWithReference], alwaysShowThumbnailFirst: Bool = false, thumbnail: Bool = false, autoFetchFullSize: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let signal = wallpaperDatas(account: account, accountManager: accountManager, fileReference: fileReference, representations: representations, alwaysShowThumbnailFirst: alwaysShowThumbnailFirst, thumbnail: thumbnail, autoFetchFullSize: autoFetchFullSize, synchronousLoad: synchronousLoad) return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return { arguments in let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.width = arguments.boundingSize.width } if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.height = arguments.boundingSize.height } 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? var imageOrientation: UIImageOrientation = .up if let fullSizeData = fullSizeData { if fullSizeComplete { let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { imageOrientation = imageOrientationFromSource(imageSource) fullSizeImage = image } } else { let imageSource = CGImageSourceCreateIncremental(nil) CGImageSourceUpdateData(imageSource, fullSizeData as CFData, fullSizeComplete) let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { imageOrientation = imageOrientationFromSource(imageSource) fullSizeImage = image } } } var thumbnailImage: CGImage? if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, 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 initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 90.0, height: 90.0)) let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) } if false, thumbnailContextFittingSize.width > thumbnailContextSize.width { let additionalContextSize = thumbnailContextFittingSize let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) additionalBlurContext.withFlippedContext { c in c.interpolationQuality = .default if let image = thumbnailContext.generateImage()?.cgImage { c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) } } imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) blurredThumbnailImage = additionalBlurContext.generateImage() } else { blurredThumbnailImage = thumbnailContext.generateImage() } } if let blurredThumbnailImage = blurredThumbnailImage, fullSizeImage == nil { let context = DrawingContext(size: blurredThumbnailImage.size, scale: blurredThumbnailImage.scale, clear: true) context.withFlippedContext { c in c.setBlendMode(.copy) if let cgImage = blurredThumbnailImage.cgImage { c.interpolationQuality = .none drawImage(context: c, image: cgImage, orientation: imageOrientation, in: CGRect(origin: CGPoint(), size: blurredThumbnailImage.size)) c.setBlendMode(.normal) } } return context } let context = DrawingContext(size: arguments.drawingSize, clear: true) context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.imageSize.width < arguments.boundingSize.width || arguments.imageSize.height < arguments.boundingSize.height { c.fill(arguments.drawingRect) } c.setBlendMode(.copy) if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage { c.interpolationQuality = .medium drawImage(context: c, image: cgImage, orientation: imageOrientation, in: fittedRect) c.setBlendMode(.normal) } if let fullSizeImage = fullSizeImage { c.interpolationQuality = .medium drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect) } } addCorners(context, arguments: arguments) return context } } } enum PatternWallpaperDrawMode { case thumbnail case fastScreen case screen } private func patternWallpaperDatas(account: Account, accountManager: AccountManager, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> { if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.representation })), let smallestIndex = representations.index(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.index(where: { $0.representation == largestRepresentation }) { let size: CGSize? switch mode { case .thumbnail: size = largestRepresentation.dimensions.fitted(CGSize(width: 640.0, height: 640.0)) case .fastScreen: size = largestRepresentation.dimensions.fitted(CGSize(width: 1280.0, height: 1280.0)) default: size = nil } let maybeFullSize = combineLatest(accountManager.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: false), account.postbox.mediaBox.cachedResourceRepresentation(largestRepresentation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: false)) let signal = maybeFullSize |> take(1) |> mapToSignal { maybeSharedData, maybeData -> Signal<(Data?, Data?, Bool), NoError> in if maybeSharedData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeSharedData.path), options: []) return .single((nil, loadedData, true)) } else if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) return .single((nil, loadedData, true)) } else { let fetchedThumbnail = fetchedMediaResource(postbox: account.postbox, reference: representations[smallestIndex].reference) let fetchedFullSize = fetchedMediaResource(postbox: account.postbox, reference: representations[largestIndex].reference) let thumbnailData = Signal { subscriber in let fetchedDisposable = fetchedThumbnail.start() let thumbnailDisposable = account.postbox.mediaBox.cachedResourceRepresentation(representations[smallestIndex].representation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: true).start(next: { next in subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) if next.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedRead) { accountManager.mediaBox.storeResourceData(representations[smallestIndex].representation.resource.id, data: data) let _ = accountManager.mediaBox.cachedResourceRepresentation(representations[smallestIndex].representation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: true).start() } }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { fetchedDisposable.dispose() thumbnailDisposable.dispose() } } let fullSizeData = Signal<(Data?, Bool), NoError> { subscriber in let fetchedFullSizeDisposable = fetchedFullSize.start() let fullSizeDisposable = account.postbox.mediaBox.cachedResourceRepresentation(representations[largestIndex].representation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: true).start(next: { next in subscriber.putNext((next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)) if next.complete, let data = try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedRead) { accountManager.mediaBox.storeResourceData(representations[largestIndex].representation.resource.id, data: data) let _ = accountManager.mediaBox.cachedResourceRepresentation(representations[largestIndex].representation.resource, representation: CachedPatternWallpaperMaskRepresentation(size: size), complete: false, fetch: true).start() } }, error: subscriber.putError, completed: subscriber.putCompletion) return ActionDisposable { fetchedFullSizeDisposable.dispose() fullSizeDisposable.dispose() } } return thumbnailData |> mapToSignal { thumbnailData in return fullSizeData |> map { (fullSizeData, complete) in return (thumbnailData, fullSizeData, complete) } } } } return signal } else { return .never() } } func patternWallpaperImage(account: Account, accountManager: AccountManager, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return patternWallpaperDatas(account: account, accountManager: accountManager, representations: representations, mode: mode, autoFetchFullSize: autoFetchFullSize) |> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) in return patternWallpaperImageInternal(thumbnailData: thumbnailData, fullSizeData: fullSizeData, fullSizeComplete: fullSizeComplete, mode: mode) } } func patternWallpaperImageInternal(thumbnailData: Data?, fullSizeData: Data?, fullSizeComplete: Bool, mode: PatternWallpaperDrawMode) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { var prominent = false if case .thumbnail = mode { prominent = true } var scale: CGFloat = 0.0 if case .fastScreen = mode { scale = max(1.0, UIScreenScale - 1.0) } return .single((thumbnailData, fullSizeData, fullSizeComplete)) |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return { arguments in let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.width = arguments.boundingSize.width } if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.height = arguments.boundingSize.height } 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, fullSizeComplete { let options = NSMutableDictionary() options[kCGImageSourceShouldCache as NSString] = false as NSNumber if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image } } if let combinedColor = arguments.emptyColor { let color = combinedColor.withAlphaComponent(1.0) let intensity = combinedColor.alpha if fullSizeImage == nil { let context = DrawingContext(size: arguments.drawingSize, scale: 1.0, clear: true) context.withFlippedContext { c in c.setBlendMode(.copy) c.setFillColor(color.cgColor) c.fill(arguments.drawingRect) } addCorners(context, arguments: arguments) return context } let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) context.withFlippedContext { c in c.setBlendMode(.copy) c.setFillColor(color.cgColor) c.fill(arguments.drawingRect) if let fullSizeImage = fullSizeImage { c.setBlendMode(.normal) c.interpolationQuality = .medium c.clip(to: fittedRect, mask: fullSizeImage) c.setFillColor(patternColor(for: color, intensity: intensity, prominent: prominent).cgColor) c.fill(arguments.drawingRect) } } addCorners(context, arguments: arguments) return context } else { return nil } } } } func patternColor(for color: UIColor, intensity: CGFloat, prominent: Bool = false) -> UIColor { var hue: CGFloat = 0.0 var saturation: CGFloat = 0.0 var brightness: CGFloat = 0.0 if color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: nil) { if saturation > 0.0 { saturation = min(1.0, saturation + 0.05 + 0.1 * (1.0 - saturation)) } if brightness > 0.45 { brightness = max(0.0, brightness * 0.65) } else { brightness = max(0.0, min(1.0, 1.0 - brightness * 0.65)) } let alpha = (prominent ? 0.5 : 0.4) * intensity return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) } return .black } func solidColor(_ color: UIColor) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return .single({ arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) context.withFlippedContext { c in c.setFillColor(color.cgColor) c.fill(arguments.drawingRect) } addCorners(context, arguments: arguments) return context }) } private func builtinWallpaperData() -> Signal { return Signal { subscriber in if let filePath = frameworkBundle.path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg"), let image = UIImage(contentsOfFile: filePath) { subscriber.putNext(image) } subscriber.putCompletion() return EmptyDisposable } |> runOn(Queue.concurrentDefaultQueue()) } func settingsBuiltinWallpaperImage(account: Account) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return builtinWallpaperData() |> map { fullSizeImage in return { arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) let drawingRect = arguments.drawingRect var fittedSize = fullSizeImage.size.aspectFilled(drawingRect.size) if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.width = arguments.boundingSize.width } if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.height = arguments.boundingSize.height } 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) context.withFlippedContext { c in c.setBlendMode(.copy) if let fullSizeImage = fullSizeImage.cgImage { c.interpolationQuality = .medium drawImage(context: c, image: fullSizeImage, orientation: .up, in: fittedRect) } } addCorners(context, arguments: arguments) return context } } } func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let thumbnail = fetchPhotoLibraryImage(localIdentifier: photoLibraryResource.localIdentifier, thumbnail: true) let fullSize = fetchPhotoLibraryImage(localIdentifier: photoLibraryResource.localIdentifier, thumbnail: false) return (thumbnail |> then(fullSize)) |> map { result in var sourceImage = result?.0 let isThumbnail = result?.1 ?? false return { arguments in let context = DrawingContext(size: arguments.drawingSize, scale: 1.0, clear: true) let dimensions = sourceImage?.size if let thumbnailImage = sourceImage?.cgImage, isThumbnail { var fittedSize = arguments.imageSize if abs(fittedSize.width - arguments.boundingSize.width).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.width = arguments.boundingSize.width } if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) { fittedSize.height = arguments.boundingSize.height } let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 100.0, height: 100.0)) let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) } imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) } if thumbnailContextFittingSize.width > thumbnailContextSize.width { let additionalContextSize = thumbnailContextFittingSize let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) additionalBlurContext.withFlippedContext { c in c.interpolationQuality = .default if let image = thumbnailContext.generateImage()?.cgImage { c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) } } imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) sourceImage = additionalBlurContext.generateImage() } else { sourceImage = thumbnailContext.generateImage() } } context.withFlippedContext { c in c.setBlendMode(.copy) if let sourceImage = sourceImage, let cgImage = sourceImage.cgImage, let dimensions = dimensions { let imageSize = dimensions.aspectFilled(arguments.drawingRect.size) let fittedRect = CGRect(origin: CGPoint(x: floor((arguments.drawingRect.size.width - imageSize.width) / 2.0), y: floor((arguments.drawingRect.size.height - imageSize.height) / 2.0)), size: imageSize) c.draw(cgImage, in: fittedRect) } } return context } } }