| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- //
- // ImageProgressive.swift
- // Kingfisher
- //
- // Created by lixiang on 2019/5/10.
- //
- // Copyright (c) 2019 Wei Wang <onevcat@gmail.com>
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- import Foundation
- import CoreGraphics
- private let sharedProcessingQueue: CallbackQueue =
- .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process"))
- public struct ImageProgressive {
-
- /// A default `ImageProgressive` could be used across.
- public static let `default` = ImageProgressive(
- isBlur: true,
- isWait: false,
- isFastestScan: true,
- scanInterval: 0
- )
-
- /// Whether to enable blur effect processing
- let isBlur: Bool
- /// Whether to wait for the scan to complete
- let isWait: Bool
- /// Whether to enable the fastest scan
- let isFastestScan: Bool
- /// Minimum time interval for each scan
- let scanInterval: TimeInterval
-
- public init(isBlur: Bool,
- isWait: Bool,
- isFastestScan: Bool,
- scanInterval: TimeInterval) {
- self.isBlur = isBlur
- self.isWait = isWait
- self.isFastestScan = isFastestScan
- self.scanInterval = scanInterval
- }
- }
- final class ImageProgressiveProvider {
-
- private let options: KingfisherParsedOptionsInfo
- private let refreshClosure: (Image) -> Void
- private let isContinueClosure: () -> Bool
- private let isFinishedClosure: () -> Bool
- private let isWait: Bool
-
- private let decoder: ImageProgressiveDecoder
- private let queue = ImageProgressiveSerialQueue(.main)
-
- init(_ options: KingfisherParsedOptionsInfo,
- isContinue: @escaping () -> Bool,
- isFinished: @escaping () -> Bool,
- refreshImage: @escaping (Image) -> Void) {
- self.options = options
- self.refreshClosure = refreshImage
- self.isContinueClosure = isContinue
- self.isFinishedClosure = isFinished
- self.decoder = ImageProgressiveDecoder(options)
- self.isWait = options.progressiveJPEG?.isWait ?? false
- }
-
- func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) {
- let interval = options.progressiveJPEG?.scanInterval ?? 0
- let isFastest = options.progressiveJPEG?.isFastestScan ?? true
-
- func add(decoder data: Data) {
- queue.add(minimum: interval) { (completion) in
- guard self.isContinueClosure() else {
- completion()
- return
- }
-
- self.decoder.decode(data, with: callbacks) { (image) in
- defer { completion() }
- guard self.isContinueClosure() else { return }
- guard self.isWait || !self.isFinishedClosure() else { return }
- guard let image = image else { return }
-
- self.refreshClosure(image)
- }
- }
- }
-
- if isFastest {
- guard let data = decoder.scanning(data) else { return }
-
- add(decoder: data)
-
- } else {
- for data in decoder.scanning(data) {
- add(decoder: data)
- }
- }
- }
-
- func finished(_ closure: @escaping () -> Void) {
- if queue.count > 0, isWait {
- queue.notify(closure)
-
- } else {
- queue.clean()
- closure()
- }
- }
-
- deinit {
- print("deinit ImageProgressiveProvider")
- }
- }
- final class ImageProgressiveDecoder {
-
- private let options: KingfisherParsedOptionsInfo
- private(set) var scannedCount: Int = 0
- private var scannedIndex = -1
-
- init(_ options: KingfisherParsedOptionsInfo) {
- self.options = options
- }
-
- func scanning(_ data: Data) -> [Data] {
- guard let _ = options.progressiveJPEG, data.kf.contains(jpeg: .SOF2) else {
- return []
- }
- guard (scannedIndex + 1) < data.count else {
- return []
- }
-
- var datas: [Data] = []
- var index = scannedIndex + 1
- var count = scannedCount
-
- while index < (data.count - 1) {
- scannedIndex = index
- // 0xFF, 0xDA - Start Of Scan
- let SOS = ImageFormat.JPEGMarker.SOS.bytes
- if data[index] == SOS[0], data[index + 1] == SOS[1] {
- datas.append(data[0 ..< index])
- count += 1
- }
- index += 1
- }
-
- // Found more scans this the previous time
- guard count > scannedCount else { return [] }
- scannedCount = count
- return datas
- }
-
- func scanning(_ data: Data) -> Data? {
- guard let _ = options.progressiveJPEG, data.kf.contains(jpeg: .SOF2) else {
- return nil
- }
- guard (scannedIndex + 1) < data.count else {
- return nil
- }
-
- var index = scannedIndex + 1
- var count = scannedCount
- var lastSOSIndex = 0
-
- while index < (data.count - 1) {
- scannedIndex = index
- // 0xFF, 0xDA - Start Of Scan
- let SOS = ImageFormat.JPEGMarker.SOS.bytes
- if data[index] == SOS[0], data[index + 1] == SOS[1] {
- lastSOSIndex = index
- count += 1
- }
- index += 1
- }
-
- // Found more scans this the previous time
- guard count > scannedCount else { return nil }
- scannedCount = count
-
- // `> 1` checks that we've received a first scan (SOS) and then received
- // and also received a second scan (SOS). This way we know that we have
- // at least one full scan available.
- guard count > 1 && lastSOSIndex > 0 else { return nil }
- return data[0 ..< lastSOSIndex]
- }
-
- func decode(_ data: Data,
- with callbacks: [SessionDataTask.TaskCallback],
- completion: @escaping (Image?) -> Void) {
- guard data.kf.contains(jpeg: .SOF2) else {
- CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
- return
- }
-
- func processing(_ data: Data) {
- let processor = ImageDataProcessor(
- data: data,
- callbacks: callbacks,
- processingQueue: options.processingQueue
- )
- processor.onImageProcessed.delegate(on: self) { (self, result) in
- guard let image = try? result.0.get() else {
- CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
- return
- }
-
- CallbackQueue.mainCurrentOrAsync.execute { completion(image) }
- }
- processor.process()
- }
-
- // Blur partial images.
- let count = scannedCount
- let isBlur = options.progressiveJPEG?.isBlur ?? false
-
- if isBlur, count < 5 {
- let queue = options.processingQueue ?? sharedProcessingQueue
- queue.execute {
- // Progressively reduce blur as we load more scans.
- let radius = max(2, 14 - count * 4)
- let image = KingfisherWrapper<Image>.image(
- data: data,
- options: self.options.imageCreatingOptions
- )
- let temp = image?.kf.blurred(withRadius: CGFloat(radius))
- processing(temp?.kf.data(format: .JPEG) ?? data)
- }
-
- } else {
- processing(data)
- }
- }
- }
- final class ImageProgressiveSerialQueue {
- typealias ClosureCallback = ((@escaping () -> Void)) -> Void
- private let queue: DispatchQueue
- private var items: [DispatchWorkItem] = []
- private var notify: (() -> Void)?
- var count: Int {
- return items.count
- }
-
- init(_ queue: DispatchQueue) {
- self.queue = queue
- }
-
- func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) {
- let completion = {
- self.queue.async {
- guard !self.items.isEmpty else { return }
-
- self.items.removeFirst()
-
- if let next = self.items.first {
- self.queue.asyncAfter(deadline: .now() + interval, execute: next)
-
- } else {
- self.notify?()
- self.notify = nil
- }
- }
- }
- let item = DispatchWorkItem {
- closure(completion)
- }
- if items.isEmpty {
- queue.asyncAfter(deadline: .now(), execute: item)
- }
- items.append(item)
- }
-
- func notify(_ closure: @escaping () -> Void) {
- self.notify = closure
- }
-
- func clean() {
- items.forEach { $0.cancel() }
- items.removeAll()
- }
- }
|