ImageProgressive.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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. /// Represents a progressive loading for images which supports this feature.
  36. public struct ImageProgressive: Sendable {
  37. /// The updating strategy when an intermediate progressive image is generated and about to be set to the hosting view.
  38. public enum UpdatingStrategy {
  39. /// Use the progressive image as it is.
  40. ///
  41. /// > It is the standard behavior when handling the progressive image.
  42. case `default`
  43. /// Discard this progressive image and keep the current displayed one.
  44. case keepCurrent
  45. /// Replace the image to a new one.
  46. ///
  47. /// If the progressive loading is initialized by a view extension in Kingfisher, the replacing image will be
  48. /// used to update the view.
  49. case replace(KFCrossPlatformImage?)
  50. }
  51. /// A default `ImageProgressive` could be used across. It blurs the progressive loading with the fastest
  52. /// scan enabled and scan interval as 0.
  53. @available(*, deprecated, message: "Getting a default `ImageProgressive` is deprecated due to its syntax semantic is not clear. Use `ImageProgressive.init` instead.", renamed: "init()")
  54. public static let `default` = ImageProgressive(
  55. isBlur: true,
  56. isFastestScan: true,
  57. scanInterval: 0
  58. )
  59. /// Indicates whether to enable blur effect processing.
  60. public var isBlur: Bool
  61. /// Indicates whether to enable the fastest scan.
  62. public var isFastestScan: Bool
  63. /// The minimum time interval for each scan.
  64. public var scanInterval: TimeInterval
  65. /// Called when an intermediate image is prepared and about to be set to the image view.
  66. ///
  67. /// If implemented, you should return an ``UpdatingStrategy`` value from this delegate. This value will be used to
  68. /// update the hosting view, if any. Otherwise, if there is no hosting view (i.e., the image retrieval is not
  69. /// happening from a view extension method), the returned ``UpdatingStrategy`` is ignored.
  70. public let onImageUpdated = Delegate<KFCrossPlatformImage, UpdatingStrategy>()
  71. /// Creates an `ImageProgressive` value with default settings.
  72. ///
  73. /// It enables progressive loading with the fastest scan enabled and a scan interval of 0, resulting in a blurred
  74. /// effect.
  75. public init() {
  76. self.init(isBlur: true, isFastestScan: true, scanInterval: 0)
  77. }
  78. /// Creates an `ImageProgressive` value with the given values.
  79. ///
  80. /// - Parameters:
  81. /// - isBlur: Indicates whether to enable blur effect processing.
  82. /// - isFastestScan: Indicates whether to enable the fastest scan.
  83. /// - scanInterval: The minimum time interval for each scan.
  84. public init(
  85. isBlur: Bool,
  86. isFastestScan: Bool,
  87. scanInterval: TimeInterval
  88. )
  89. {
  90. self.isBlur = isBlur
  91. self.isFastestScan = isFastestScan
  92. self.scanInterval = scanInterval
  93. }
  94. }
  95. // A data receiving provider to update the image. Working with an `ImageProgressive`, it helps to implement the image
  96. // progressive effect.
  97. final class ImageProgressiveProvider: DataReceivingSideEffect, @unchecked Sendable {
  98. private let propertyQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageProgressiveProviderPropertyQueue")
  99. private var _onShouldApply: () -> Bool = { return true }
  100. var onShouldApply: () -> Bool {
  101. get { propertyQueue.sync { _onShouldApply } }
  102. set { propertyQueue.sync { _onShouldApply = newValue } }
  103. }
  104. func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) {
  105. DispatchQueue.main.async {
  106. guard self.onShouldApply() else { return }
  107. self.update(data: task.mutableData, with: task.callbacks)
  108. }
  109. }
  110. private let progressive: ImageProgressive
  111. private let refresh: (KFCrossPlatformImage) -> Void
  112. private let decoder: ImageProgressiveDecoder
  113. private let queue = ImageProgressiveSerialQueue()
  114. init?(
  115. options: KingfisherParsedOptionsInfo,
  116. refresh: @escaping (KFCrossPlatformImage) -> Void
  117. ) {
  118. guard let progressive = options.progressiveJPEG else { return nil }
  119. self.progressive = progressive
  120. self.refresh = refresh
  121. self.decoder = ImageProgressiveDecoder(
  122. progressive,
  123. processingQueue: options.processingQueue ?? sharedProcessingQueue,
  124. creatingOptions: options.imageCreatingOptions
  125. )
  126. }
  127. func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) {
  128. guard !data.isEmpty else { return }
  129. queue.add(minimum: progressive.scanInterval) { completion in
  130. func decode(_ data: Data) {
  131. self.decoder.decode(data, with: callbacks) { image in
  132. defer { completion() }
  133. guard self.onShouldApply() else { return }
  134. guard let image = image else { return }
  135. self.refresh(image)
  136. }
  137. }
  138. let semaphore = DispatchSemaphore(value: 0)
  139. var onShouldApply: Bool = false
  140. CallbackQueue.mainAsync.execute {
  141. onShouldApply = self.onShouldApply()
  142. semaphore.signal()
  143. }
  144. semaphore.wait()
  145. guard onShouldApply else {
  146. self.queue.clean()
  147. completion()
  148. return
  149. }
  150. if self.progressive.isFastestScan {
  151. decode(self.decoder.scanning(data) ?? Data())
  152. } else {
  153. self.decoder.scanning(data).forEach { decode($0) }
  154. }
  155. }
  156. }
  157. }
  158. private final class ImageProgressiveDecoder {
  159. private let option: ImageProgressive
  160. private let processingQueue: CallbackQueue
  161. private let creatingOptions: ImageCreatingOptions
  162. private(set) var scannedCount = 0
  163. private(set) var scannedIndex = -1
  164. init(_ option: ImageProgressive,
  165. processingQueue: CallbackQueue,
  166. creatingOptions: ImageCreatingOptions) {
  167. self.option = option
  168. self.processingQueue = processingQueue
  169. self.creatingOptions = creatingOptions
  170. }
  171. func scanning(_ data: Data) -> [Data] {
  172. guard data.kf.contains(jpeg: .SOF2) else {
  173. return []
  174. }
  175. guard scannedIndex + 1 < data.count else {
  176. return []
  177. }
  178. var datas: [Data] = []
  179. var index = scannedIndex + 1
  180. var count = scannedCount
  181. while index < data.count - 1 {
  182. scannedIndex = index
  183. // 0xFF, 0xDA - Start Of Scan
  184. let SOS = ImageFormat.JPEGMarker.SOS.bytes
  185. if data[index] == SOS[0], data[index + 1] == SOS[1] {
  186. if count > 0 {
  187. datas.append(data[0 ..< index])
  188. }
  189. count += 1
  190. }
  191. index += 1
  192. }
  193. // Found more scans this the previous time
  194. guard count > scannedCount else { return [] }
  195. scannedCount = count
  196. // `> 1` checks that we've received a first scan (SOS) and then received
  197. // and also received a second scan (SOS). This way we know that we have
  198. // at least one full scan available.
  199. guard count > 1 else { return [] }
  200. return datas
  201. }
  202. func scanning(_ data: Data) -> Data? {
  203. guard data.kf.contains(jpeg: .SOF2) else {
  204. return nil
  205. }
  206. guard scannedIndex + 1 < data.count else {
  207. return nil
  208. }
  209. var index = scannedIndex + 1
  210. var count = scannedCount
  211. var lastSOSIndex = 0
  212. while index < data.count - 1 {
  213. scannedIndex = index
  214. // 0xFF, 0xDA - Start Of Scan
  215. let SOS = ImageFormat.JPEGMarker.SOS.bytes
  216. if data[index] == SOS[0], data[index + 1] == SOS[1] {
  217. lastSOSIndex = index
  218. count += 1
  219. }
  220. index += 1
  221. }
  222. // Found more scans this the previous time
  223. guard count > scannedCount else { return nil }
  224. scannedCount = count
  225. // `> 1` checks that we've received a first scan (SOS) and then received
  226. // and also received a second scan (SOS). This way we know that we have
  227. // at least one full scan available.
  228. guard count > 1 && lastSOSIndex > 0 else { return nil }
  229. return data[0 ..< lastSOSIndex]
  230. }
  231. func decode(_ data: Data,
  232. with callbacks: [SessionDataTask.TaskCallback],
  233. completion: @escaping (KFCrossPlatformImage?) -> Void) {
  234. guard data.kf.contains(jpeg: .SOF2) else {
  235. CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
  236. return
  237. }
  238. func processing(_ data: Data) {
  239. let processor = ImageDataProcessor(
  240. data: data,
  241. callbacks: callbacks,
  242. processingQueue: processingQueue
  243. )
  244. processor.onImageProcessed.delegate(on: self) { (self, result) in
  245. guard let image = try? result.0.get() else {
  246. CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
  247. return
  248. }
  249. CallbackQueue.mainCurrentOrAsync.execute { completion(image) }
  250. }
  251. processor.process()
  252. }
  253. // Blur partial images.
  254. let count = scannedCount
  255. if option.isBlur, count < 6 {
  256. processingQueue.execute {
  257. // Progressively reduce blur as we load more scans.
  258. let image = KingfisherWrapper<KFCrossPlatformImage>.image(
  259. data: data,
  260. options: self.creatingOptions
  261. )
  262. let radius = max(2, 14 - count * 4)
  263. let temp = image?.kf.blurred(withRadius: CGFloat(radius))
  264. processing(temp?.kf.data(format: .JPEG) ?? data)
  265. }
  266. } else {
  267. processing(data)
  268. }
  269. }
  270. }
  271. private final class ImageProgressiveSerialQueue: @unchecked Sendable {
  272. typealias ClosureCallback = @Sendable ((@escaping () -> Void)) -> Void
  273. private let queue: DispatchQueue
  274. private var items: [DispatchWorkItem] = []
  275. private var notify: (() -> Void)?
  276. private var lastTime: TimeInterval?
  277. init() {
  278. self.queue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageProgressive.SerialQueue")
  279. }
  280. func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) {
  281. let completion = { @Sendable [weak self] in
  282. guard let self = self else { return }
  283. self.queue.async { [weak self] in
  284. guard let self = self else { return }
  285. guard !self.items.isEmpty else { return }
  286. self.items.removeFirst()
  287. if let next = self.items.first {
  288. self.queue.asyncAfter(
  289. deadline: .now() + interval,
  290. execute: next
  291. )
  292. } else {
  293. self.lastTime = Date().timeIntervalSince1970
  294. self.notify?()
  295. self.notify = nil
  296. }
  297. }
  298. }
  299. queue.async { [weak self] in
  300. guard let self = self else { return }
  301. let item = DispatchWorkItem {
  302. closure(completion)
  303. }
  304. if self.items.isEmpty {
  305. let difference = Date().timeIntervalSince1970 - (self.lastTime ?? 0)
  306. let delay = difference < interval ? interval - difference : 0
  307. self.queue.asyncAfter(deadline: .now() + delay, execute: item)
  308. }
  309. self.items.append(item)
  310. }
  311. }
  312. func clean() {
  313. queue.async { [weak self] in
  314. guard let self = self else { return }
  315. self.items.forEach { $0.cancel() }
  316. self.items.removeAll()
  317. }
  318. }
  319. }