Swiftgram/submodules/TelegramUI/TelegramUI/WallpaperResources.swift
2019-08-02 02:26:53 +03:00

585 lines
34 KiB
Swift

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<MediaResourceData, NoError>
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<MediaResourceData, NoError> 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<MediaResourceData, NoError> 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<MediaResourceData, NoError> 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<FetchResourceSourceType, FetchResourceError>
if let _ = decodedThumbnailData {
fetchedThumbnail = .complete()
} else {
fetchedThumbnail = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: representations[smallestIndex].reference)
}
let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: representations[largestIndex].reference)
let thumbnailData: Signal<Data?, NoError>
if let decodedThumbnailData = decodedThumbnailData {
thumbnailData = .single(decodedThumbnailData)
} else {
thumbnailData = Signal<Data?, NoError> { 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: UIImage.Orientation = .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(mediaBox: account.postbox.mediaBox, reference: representations[smallestIndex].reference)
let fetchedFullSize = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: representations[largestIndex].reference)
let thumbnailData = Signal<Data?, NoError> { 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<UIImage, NoError> {
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
}
}
}