KFOptionSetter+.swift (4859B)
1 // 2 // KFOptionSetter+.swift 3 // damus 4 // 5 // Created by Oleg Abalonski on 2/15/23. 6 // 7 8 import UIKit 9 import Kingfisher 10 11 extension KFOptionSetter { 12 13 func imageContext(_ imageContext: ImageContext) -> Self { 14 options.callbackQueue = .dispatch(.global(qos: .background)) 15 options.processingQueue = .dispatch(.global(qos: .background)) 16 options.downloader = CustomImageDownloader.shared 17 options.processor = CustomImageProcessor( 18 maxSize: imageContext.maxMebibyteSize(), 19 downsampleSize: imageContext.downsampleSize() 20 ) 21 options.cacheSerializer = CustomCacheSerializer( 22 maxSize: imageContext.maxMebibyteSize(), 23 downsampleSize: imageContext.downsampleSize() 24 ) 25 options.backgroundDecode = true 26 options.cacheOriginalImage = true 27 options.scaleFactor = UIScreen.main.scale 28 options.onlyLoadFirstFrame = should_disable_image_animation() 29 30 return self 31 } 32 33 func onFailure(fallbackUrl: URL?, cacheKey: String?) -> Self { 34 guard let url = fallbackUrl, let key = cacheKey else { return self } 35 let imageResource = ImageResource(downloadURL: url, cacheKey: key) 36 let source = imageResource.convertToSource() 37 options.alternativeSources = [source] 38 39 return self 40 } 41 } 42 43 let MAX_FILE_SIZE = 20_971_520 // 20MiB 44 45 enum ImageContext { 46 case pfp 47 case banner 48 case note 49 50 func maxMebibyteSize() -> Int { 51 switch self { 52 case .pfp: 53 return 5_242_880 // 5Mib 54 case .banner, .note: 55 return 20_971_520 // 20MiB 56 } 57 } 58 59 func downsampleSize() -> CGSize { 60 switch self { 61 case .pfp: 62 return CGSize(width: 200, height: 200) 63 case .banner: 64 return CGSize(width: 750, height: 250) 65 case .note: 66 return CGSize(width: 500, height: 500) 67 } 68 } 69 } 70 71 struct CustomImageProcessor: ImageProcessor { 72 73 let maxSize: Int 74 let downsampleSize: CGSize 75 76 let identifier = "com.damus.customimageprocessor" 77 78 func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? { 79 80 switch item { 81 case .image(_): 82 // This case will never run 83 return DefaultImageProcessor.default.process(item: item, options: options) 84 case .data(let data): 85 86 // Handle large image size 87 if data.count > maxSize { 88 return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor) 89 } 90 91 // Handle SVG image 92 if let dataString = String(data: data, encoding: .utf8), 93 let svg = SVG(dataString) { 94 95 let render = UIGraphicsImageRenderer(size: svg.size) 96 let image = render.image { context in 97 svg.draw(in: context.cgContext) 98 } 99 100 return image.kf.scaled(to: options.scaleFactor) 101 } 102 103 return DefaultImageProcessor.default.process(item: item, options: options) 104 } 105 } 106 } 107 108 struct CustomCacheSerializer: CacheSerializer { 109 110 let maxSize: Int 111 let downsampleSize: CGSize 112 113 func data(with image: Kingfisher.KFCrossPlatformImage, original: Data?) -> Data? { 114 return DefaultCacheSerializer.default.data(with: image, original: original) 115 } 116 117 func image(with data: Data, options: Kingfisher.KingfisherParsedOptionsInfo) -> Kingfisher.KFCrossPlatformImage? { 118 if data.count > maxSize { 119 return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor) 120 } 121 122 return DefaultCacheSerializer.default.image(with: data, options: options) 123 } 124 } 125 126 class CustomSessionDelegate: SessionDelegate { 127 override func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { 128 let contentLength = response.expectedContentLength 129 130 // Content-Length header is optional (-1 when missing) 131 if (contentLength != -1 && contentLength > MAX_FILE_SIZE) { 132 return super.urlSession(session, dataTask: dataTask, didReceive: URLResponse(), completionHandler: completionHandler) 133 } 134 135 super.urlSession(session, dataTask: dataTask, didReceive: response, completionHandler: completionHandler) 136 } 137 } 138 139 class CustomImageDownloader: ImageDownloader { 140 141 static let shared = CustomImageDownloader(name: "shared") 142 143 override init(name: String) { 144 super.init(name: name) 145 sessionDelegate = CustomSessionDelegate() 146 } 147 }