You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

321 lines
11 KiB

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