ImageProgressive.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  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. private let sharedProcessingQueue: CallbackQueue =
  29. .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process"))
  30. public struct ImageProgressive {
  31. /// A default `ImageProgressive` could be used across. It blurs the progressive loading with the fastest
  32. /// scan enabled and scan interval as 0.
  33. public static let `default` = ImageProgressive(
  34. isBlur: true,
  35. isFastestScan: true,
  36. scanInterval: 0
  37. )
  38. /// Whether to enable blur effect processing
  39. let isBlur: Bool
  40. /// Whether to enable the fastest scan
  41. let isFastestScan: Bool
  42. /// Minimum time interval for each scan
  43. let scanInterval: TimeInterval
  44. public let onImageUpdated = Delegate<KFCrossPlatformImage, Void>()
  45. public init(isBlur: Bool,
  46. isFastestScan: Bool,
  47. scanInterval: TimeInterval
  48. )
  49. {
  50. self.isBlur = isBlur
  51. self.isFastestScan = isFastestScan
  52. self.scanInterval = scanInterval
  53. }
  54. }
  55. protocol ImageSettable: AnyObject {
  56. var image: KFCrossPlatformImage? { get set }
  57. }
  58. final class ImageProgressiveProvider: DataReceivingSideEffect {
  59. var onShouldApply: () -> Bool = { return true }
  60. func onDataReceived(_ session: URLSession, task: SessionDataTask, data: Data) {
  61. DispatchQueue.main.async {
  62. guard self.onShouldApply() else { return }
  63. self.update(data: task.mutableData, with: task.callbacks)
  64. }
  65. }
  66. private let option: ImageProgressive
  67. private let refresh: (KFCrossPlatformImage) -> Void
  68. private let decoder: ImageProgressiveDecoder
  69. private let queue = ImageProgressiveSerialQueue()
  70. init?(_ options: KingfisherParsedOptionsInfo,
  71. refresh: @escaping (KFCrossPlatformImage) -> Void) {
  72. guard let option = options.progressiveJPEG else { return nil }
  73. self.option = option
  74. self.refresh = refresh
  75. self.decoder = ImageProgressiveDecoder(
  76. option,
  77. processingQueue: options.processingQueue ?? sharedProcessingQueue,
  78. creatingOptions: options.imageCreatingOptions
  79. )
  80. }
  81. func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) {
  82. guard !data.isEmpty else { return }
  83. queue.add(minimum: option.scanInterval) { completion in
  84. func decode(_ data: Data) {
  85. self.decoder.decode(data, with: callbacks) { image in
  86. defer { completion() }
  87. guard self.onShouldApply() else { return }
  88. guard let image = image else { return }
  89. self.refresh(image)
  90. }
  91. }
  92. let semaphore = DispatchSemaphore(value: 0)
  93. var onShouldApply: Bool = false
  94. CallbackQueue.mainAsync.execute {
  95. onShouldApply = self.onShouldApply()
  96. semaphore.signal()
  97. }
  98. semaphore.wait()
  99. guard onShouldApply else {
  100. self.queue.clean()
  101. completion()
  102. return
  103. }
  104. if self.option.isFastestScan {
  105. decode(self.decoder.scanning(data) ?? Data())
  106. } else {
  107. self.decoder.scanning(data).forEach { decode($0) }
  108. }
  109. }
  110. }
  111. }
  112. private final class ImageProgressiveDecoder {
  113. private let option: ImageProgressive
  114. private let processingQueue: CallbackQueue
  115. private let creatingOptions: ImageCreatingOptions
  116. private(set) var scannedCount = 0
  117. private(set) var scannedIndex = -1
  118. init(_ option: ImageProgressive,
  119. processingQueue: CallbackQueue,
  120. creatingOptions: ImageCreatingOptions) {
  121. self.option = option
  122. self.processingQueue = processingQueue
  123. self.creatingOptions = creatingOptions
  124. }
  125. func scanning(_ data: Data) -> [Data] {
  126. guard data.kf.contains(jpeg: .SOF2) else {
  127. return []
  128. }
  129. guard scannedIndex + 1 < data.count else {
  130. return []
  131. }
  132. var datas: [Data] = []
  133. var index = scannedIndex + 1
  134. var count = scannedCount
  135. while index < data.count - 1 {
  136. scannedIndex = index
  137. // 0xFF, 0xDA - Start Of Scan
  138. let SOS = ImageFormat.JPEGMarker.SOS.bytes
  139. if data[index] == SOS[0], data[index + 1] == SOS[1] {
  140. if count > 0 {
  141. datas.append(data[0 ..< index])
  142. }
  143. count += 1
  144. }
  145. index += 1
  146. }
  147. // Found more scans this the previous time
  148. guard count > scannedCount else { return [] }
  149. scannedCount = count
  150. // `> 1` checks that we've received a first scan (SOS) and then received
  151. // and also received a second scan (SOS). This way we know that we have
  152. // at least one full scan available.
  153. guard count > 1 else { return [] }
  154. return datas
  155. }
  156. func scanning(_ data: Data) -> Data? {
  157. guard data.kf.contains(jpeg: .SOF2) else {
  158. return nil
  159. }
  160. guard scannedIndex + 1 < data.count else {
  161. return nil
  162. }
  163. var index = scannedIndex + 1
  164. var count = scannedCount
  165. var lastSOSIndex = 0
  166. while index < data.count - 1 {
  167. scannedIndex = index
  168. // 0xFF, 0xDA - Start Of Scan
  169. let SOS = ImageFormat.JPEGMarker.SOS.bytes
  170. if data[index] == SOS[0], data[index + 1] == SOS[1] {
  171. lastSOSIndex = index
  172. count += 1
  173. }
  174. index += 1
  175. }
  176. // Found more scans this the previous time
  177. guard count > scannedCount else { return nil }
  178. scannedCount = count
  179. // `> 1` checks that we've received a first scan (SOS) and then received
  180. // and also received a second scan (SOS). This way we know that we have
  181. // at least one full scan available.
  182. guard count > 1 && lastSOSIndex > 0 else { return nil }
  183. return data[0 ..< lastSOSIndex]
  184. }
  185. func decode(_ data: Data,
  186. with callbacks: [SessionDataTask.TaskCallback],
  187. completion: @escaping (KFCrossPlatformImage?) -> Void) {
  188. guard data.kf.contains(jpeg: .SOF2) else {
  189. CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
  190. return
  191. }
  192. func processing(_ data: Data) {
  193. let processor = ImageDataProcessor(
  194. data: data,
  195. callbacks: callbacks,
  196. processingQueue: processingQueue
  197. )
  198. processor.onImageProcessed.delegate(on: self) { (self, result) in
  199. guard let image = try? result.0.get() else {
  200. CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
  201. return
  202. }
  203. CallbackQueue.mainCurrentOrAsync.execute { completion(image) }
  204. }
  205. processor.process()
  206. }
  207. // Blur partial images.
  208. let count = scannedCount
  209. if option.isBlur, count < 6 {
  210. processingQueue.execute {
  211. // Progressively reduce blur as we load more scans.
  212. let image = KingfisherWrapper<KFCrossPlatformImage>.image(
  213. data: data,
  214. options: self.creatingOptions
  215. )
  216. let radius = max(2, 14 - count * 4)
  217. let temp = image?.kf.blurred(withRadius: CGFloat(radius))
  218. processing(temp?.kf.data(format: .JPEG) ?? data)
  219. }
  220. } else {
  221. processing(data)
  222. }
  223. }
  224. }
  225. private final class ImageProgressiveSerialQueue {
  226. typealias ClosureCallback = ((@escaping () -> Void)) -> Void
  227. private let queue: DispatchQueue
  228. private var items: [DispatchWorkItem] = []
  229. private var notify: (() -> Void)?
  230. private var lastTime: TimeInterval?
  231. var count: Int { return items.count }
  232. init() {
  233. self.queue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageProgressive.SerialQueue")
  234. }
  235. func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) {
  236. let completion = { [weak self] in
  237. guard let self = self else { return }
  238. self.queue.async { [weak self] in
  239. guard let self = self else { return }
  240. guard !self.items.isEmpty else { return }
  241. self.items.removeFirst()
  242. if let next = self.items.first {
  243. self.queue.asyncAfter(
  244. deadline: .now() + interval,
  245. execute: next
  246. )
  247. } else {
  248. self.lastTime = Date().timeIntervalSince1970
  249. self.notify?()
  250. self.notify = nil
  251. }
  252. }
  253. }
  254. queue.async { [weak self] in
  255. guard let self = self else { return }
  256. let item = DispatchWorkItem {
  257. closure(completion)
  258. }
  259. if self.items.isEmpty {
  260. let difference = Date().timeIntervalSince1970 - (self.lastTime ?? 0)
  261. let delay = difference < interval ? interval - difference : 0
  262. self.queue.asyncAfter(deadline: .now() + delay, execute: item)
  263. }
  264. self.items.append(item)
  265. }
  266. }
  267. func notify(_ closure: @escaping () -> Void) {
  268. self.notify = closure
  269. }
  270. func clean() {
  271. queue.async { [weak self] in
  272. guard let self = self else { return }
  273. self.items.forEach { $0.cancel() }
  274. self.items.removeAll()
  275. }
  276. }
  277. }