ImageProgressive.swift 11 KB

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