ImageProgressive.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. //
  2. // ImageProgressive.swift
  3. // Kingfisher
  4. //
  5. // Created by lixiang on 2019/5/10.
  6. //
  7. // Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import Foundation
  27. import CoreGraphics
  28. #if os(macOS)
  29. import AppKit
  30. #else
  31. import UIKit
  32. #endif
  33. private let sharedProcessingQueue: CallbackQueue =
  34. .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process"))
  35. public struct ImageProgressive {
  36. /// The updating strategy when an intermediate progressive image is generated and about to be set to the hosting view.
  37. ///
  38. /// - default: Use the progressive image as it is. It is the standard behavior when handling the progressive image.
  39. /// - keepCurrent: Discard this progressive image and keep the current displayed one.
  40. /// - replace: Replace the image to a new one. If the progressive loading is initialized by a view extension in
  41. /// Kingfisher, the replacing image will be used to update the view.
  42. public enum UpdatingStrategy {
  43. case `default`
  44. case keepCurrent
  45. case replace(KFCrossPlatformImage?)
  46. }
  47. /// A default `ImageProgressive` could be used across. It blurs the progressive loading with the fastest
  48. /// scan enabled and scan interval as 0.
  49. @available(*, deprecated, message: "Getting a default `ImageProgressive` is deprecated due to its syntax symatic is not clear. Use `ImageProgressive.init` instead.", renamed: "init()")
  50. public static let `default` = ImageProgressive(
  51. isBlur: true,
  52. isFastestScan: true,
  53. scanInterval: 0
  54. )
  55. /// Whether to enable blur effect processing
  56. let isBlur: Bool
  57. /// Whether to enable the fastest scan
  58. let isFastestScan: Bool
  59. /// Minimum time interval for each scan
  60. let scanInterval: TimeInterval
  61. /// Called when an intermediate image is prepared and about to be set to the image view. The return value of this
  62. /// delegate will be used to update the hosting view, if any. Otherwise, if there is no hosting view (a.k.a the
  63. /// image retrieving is not happening from a view extension method), the returned `UpdatingStrategy` is ignored.
  64. public let onImageUpdated = Delegate<KFCrossPlatformImage, UpdatingStrategy>()
  65. /// Creates an `ImageProgressive` value with default sets. It blurs the progressive loading with the fastest
  66. /// scan enabled and scan interval as 0.
  67. public init() {
  68. self.init(isBlur: true, isFastestScan: true, scanInterval: 0)
  69. }
  70. /// Creates an `ImageProgressive` value the given values.
  71. /// - Parameters:
  72. /// - isBlur: Whether to enable blur effect processing.
  73. /// - isFastestScan: Whether to enable the fastest scan.
  74. /// - scanInterval: Minimum time interval for each scan.
  75. public init(isBlur: Bool,
  76. isFastestScan: Bool,
  77. scanInterval: TimeInterval
  78. )
  79. {
  80. self.isBlur = isBlur
  81. self.isFastestScan = isFastestScan
  82. self.scanInterval = scanInterval
  83. }
  84. }
  85. final class ImageProgressiveProvider: DataReceivingSideEffect {
  86. var onShouldApply: () -> Bool = { return true }
  87. func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) {
  88. DispatchQueue.main.async {
  89. guard self.onShouldApply() else { return }
  90. self.update(data: task.mutableData, with: task.callbacks)
  91. }
  92. }
  93. private let option: ImageProgressive
  94. private let refresh: (KFCrossPlatformImage) -> Void
  95. private let decoder: ImageProgressiveDecoder
  96. private let queue = ImageProgressiveSerialQueue()
  97. init?(_ options: KingfisherParsedOptionsInfo,
  98. refresh: @escaping (KFCrossPlatformImage) -> Void) {
  99. guard let option = options.progressiveJPEG else { return nil }
  100. self.option = option
  101. self.refresh = refresh
  102. self.decoder = ImageProgressiveDecoder(
  103. option,
  104. processingQueue: options.processingQueue ?? sharedProcessingQueue,
  105. creatingOptions: options.imageCreatingOptions
  106. )
  107. }
  108. func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) {
  109. guard !data.isEmpty else { return }
  110. queue.add(minimum: option.scanInterval) { completion in
  111. func decode(_ data: Data) {
  112. self.decoder.decode(data, with: callbacks) { image in
  113. defer { completion() }
  114. guard self.onShouldApply() else { return }
  115. guard let image = image else { return }
  116. self.refresh(image)
  117. }
  118. }
  119. let semaphore = DispatchSemaphore(value: 0)
  120. var onShouldApply: Bool = false
  121. CallbackQueue.mainAsync.execute {
  122. onShouldApply = self.onShouldApply()
  123. semaphore.signal()
  124. }
  125. semaphore.wait()
  126. guard onShouldApply else {
  127. self.queue.clean()
  128. completion()
  129. return
  130. }
  131. if self.option.isFastestScan {
  132. decode(self.decoder.scanning(data) ?? Data())
  133. } else {
  134. self.decoder.scanning(data).forEach { decode($0) }
  135. }
  136. }
  137. }
  138. }
  139. private final class ImageProgressiveDecoder {
  140. private let option: ImageProgressive
  141. private let processingQueue: CallbackQueue
  142. private let creatingOptions: ImageCreatingOptions
  143. private(set) var scannedCount = 0
  144. private(set) var scannedIndex = -1
  145. init(_ option: ImageProgressive,
  146. processingQueue: CallbackQueue,
  147. creatingOptions: ImageCreatingOptions) {
  148. self.option = option
  149. self.processingQueue = processingQueue
  150. self.creatingOptions = creatingOptions
  151. }
  152. func scanning(_ data: Data) -> [Data] {
  153. guard data.kf.contains(jpeg: .SOF2) else {
  154. return []
  155. }
  156. guard scannedIndex + 1 < data.count else {
  157. return []
  158. }
  159. var datas: [Data] = []
  160. var index = scannedIndex + 1
  161. var count = scannedCount
  162. while index < data.count - 1 {
  163. scannedIndex = index
  164. // 0xFF, 0xDA - Start Of Scan
  165. let SOS = ImageFormat.JPEGMarker.SOS.bytes
  166. if data[index] == SOS[0], data[index + 1] == SOS[1] {
  167. if count > 0 {
  168. datas.append(data[0 ..< index])
  169. }
  170. count += 1
  171. }
  172. index += 1
  173. }
  174. // Found more scans this the previous time
  175. guard count > scannedCount else { return [] }
  176. scannedCount = count
  177. // `> 1` checks that we've received a first scan (SOS) and then received
  178. // and also received a second scan (SOS). This way we know that we have
  179. // at least one full scan available.
  180. guard count > 1 else { return [] }
  181. return datas
  182. }
  183. func scanning(_ data: Data) -> Data? {
  184. guard data.kf.contains(jpeg: .SOF2) else {
  185. return nil
  186. }
  187. guard scannedIndex + 1 < data.count else {
  188. return nil
  189. }
  190. var index = scannedIndex + 1
  191. var count = scannedCount
  192. var lastSOSIndex = 0
  193. while index < data.count - 1 {
  194. scannedIndex = index
  195. // 0xFF, 0xDA - Start Of Scan
  196. let SOS = ImageFormat.JPEGMarker.SOS.bytes
  197. if data[index] == SOS[0], data[index + 1] == SOS[1] {
  198. lastSOSIndex = index
  199. count += 1
  200. }
  201. index += 1
  202. }
  203. // Found more scans this the previous time
  204. guard count > scannedCount else { return nil }
  205. scannedCount = count
  206. // `> 1` checks that we've received a first scan (SOS) and then received
  207. // and also received a second scan (SOS). This way we know that we have
  208. // at least one full scan available.
  209. guard count > 1 && lastSOSIndex > 0 else { return nil }
  210. return data[0 ..< lastSOSIndex]
  211. }
  212. func decode(_ data: Data,
  213. with callbacks: [SessionDataTask.TaskCallback],
  214. completion: @escaping (KFCrossPlatformImage?) -> Void) {
  215. guard data.kf.contains(jpeg: .SOF2) else {
  216. CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
  217. return
  218. }
  219. func processing(_ data: Data) {
  220. let processor = ImageDataProcessor(
  221. data: data,
  222. callbacks: callbacks,
  223. processingQueue: processingQueue
  224. )
  225. processor.onImageProcessed.delegate(on: self) { (self, result) in
  226. guard let image = try? result.0.get() else {
  227. CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
  228. return
  229. }
  230. CallbackQueue.mainCurrentOrAsync.execute { completion(image) }
  231. }
  232. processor.process()
  233. }
  234. // Blur partial images.
  235. let count = scannedCount
  236. if option.isBlur, count < 6 {
  237. processingQueue.execute {
  238. // Progressively reduce blur as we load more scans.
  239. let image = KingfisherWrapper<KFCrossPlatformImage>.image(
  240. data: data,
  241. options: self.creatingOptions
  242. )
  243. let radius = max(2, 14 - count * 4)
  244. let temp = image?.kf.blurred(withRadius: CGFloat(radius))
  245. processing(temp?.kf.data(format: .JPEG) ?? data)
  246. }
  247. } else {
  248. processing(data)
  249. }
  250. }
  251. }
  252. private final class ImageProgressiveSerialQueue {
  253. typealias ClosureCallback = ((@escaping () -> Void)) -> Void
  254. private let queue: DispatchQueue
  255. private var items: [DispatchWorkItem] = []
  256. private var notify: (() -> Void)?
  257. private var lastTime: TimeInterval?
  258. init() {
  259. self.queue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageProgressive.SerialQueue")
  260. }
  261. func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) {
  262. let completion = { [weak self] in
  263. guard let self = self else { return }
  264. self.queue.async { [weak self] in
  265. guard let self = self else { return }
  266. guard !self.items.isEmpty else { return }
  267. self.items.removeFirst()
  268. if let next = self.items.first {
  269. self.queue.asyncAfter(
  270. deadline: .now() + interval,
  271. execute: next
  272. )
  273. } else {
  274. self.lastTime = Date().timeIntervalSince1970
  275. self.notify?()
  276. self.notify = nil
  277. }
  278. }
  279. }
  280. queue.async { [weak self] in
  281. guard let self = self else { return }
  282. let item = DispatchWorkItem {
  283. closure(completion)
  284. }
  285. if self.items.isEmpty {
  286. let difference = Date().timeIntervalSince1970 - (self.lastTime ?? 0)
  287. let delay = difference < interval ? interval - difference : 0
  288. self.queue.asyncAfter(deadline: .now() + delay, execute: item)
  289. }
  290. self.items.append(item)
  291. }
  292. }
  293. func clean() {
  294. queue.async { [weak self] in
  295. guard let self = self else { return }
  296. self.items.forEach { $0.cancel() }
  297. self.items.removeAll()
  298. }
  299. }
  300. }