KFOptionsSetter.swift 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. //
  2. // KFOptionsSetter.swift
  3. // Kingfisher
  4. //
  5. // Created by onevcat on 2020/12/22.
  6. //
  7. // Copyright (c) 2020 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. /// A protocol that Kingfisher can use to perform chained setting in builder pattern.
  34. @MainActor
  35. public protocol KFOptionSetter {
  36. var options: KingfisherParsedOptionsInfo { get nonmutating set }
  37. var onFailureDelegate: Delegate<KingfisherError, Void> { get }
  38. var onSuccessDelegate: Delegate<RetrieveImageResult, Void> { get }
  39. var onProgressDelegate: Delegate<(Int64, Int64), Void> { get }
  40. }
  41. extension KF.Builder: KFOptionSetter { }
  42. class KFDelegateObserver: @unchecked Sendable {
  43. static let `default` = KFDelegateObserver()
  44. }
  45. // MARK: - Life cycles
  46. extension KFOptionSetter {
  47. /// Sets the progress block to current builder.
  48. ///
  49. /// - Parameter block:
  50. /// Called when the image downloading progress gets updated. If the response does not contain an
  51. /// [`expectedContentLength`](https://developer.apple.com/documentation/foundation/urlresponse/1413507-expectedcontentlength)
  52. /// in the received `URLResponse`, this block will not be called. If `block` is `nil`, the callback will be reset.
  53. ///
  54. /// - Returns: A `Self` value with changes applied.
  55. ///
  56. public func onProgress(_ block: DownloadProgressBlock?) -> Self {
  57. onProgressDelegate.delegate(on: KFDelegateObserver.default) { (_, result) in
  58. block?(result.0, result.1)
  59. }
  60. return self
  61. }
  62. /// Sets the done block to current builder.
  63. /// - Parameter block: Called when the image task successfully completes and the image set is done. If `block`
  64. /// is `nil`, the callback will be reset.
  65. /// - Returns: A `Self` with changes applied.
  66. ///
  67. public func onSuccess(_ block: ((RetrieveImageResult) -> Void)?) -> Self {
  68. onSuccessDelegate.delegate(on: KFDelegateObserver.default) { (_, result) in
  69. block?(result)
  70. }
  71. return self
  72. }
  73. /// Sets the catch block to current builder.
  74. /// - Parameter block: Called when an error happens during the image task. If `block`
  75. /// is `nil`, the callback will be reset.
  76. /// - Returns: A `Self` with changes applied.
  77. ///
  78. public func onFailure(_ block: ((KingfisherError) -> Void)?) -> Self {
  79. onFailureDelegate.delegate(on: KFDelegateObserver.default) { (_, error) in
  80. block?(error)
  81. }
  82. return self
  83. }
  84. }
  85. // MARK: - Basic options settings.
  86. extension KFOptionSetter {
  87. /// Sets the target image cache for this task.
  88. ///
  89. /// - Parameter cache: The target cache to be used for the task.
  90. /// - Returns: A `Self` value with changes applied.
  91. ///
  92. /// Kingfisher will utilize the associated ``ImageCache`` object when performing related operations,
  93. /// such as attempting to retrieve cached images and storing downloaded images within it.
  94. ///
  95. public func targetCache(_ cache: ImageCache) -> Self {
  96. options.targetCache = cache
  97. return self
  98. }
  99. /// Sets the target image cache to store the original downloaded image for this task.
  100. ///
  101. /// - Parameter cache: The target cache is about to be used for storing the original downloaded image from the task.
  102. /// - Returns: A `Self` value with changes applied.
  103. ///
  104. /// The ``ImageCache`` for storing and retrieving original images. If ``KingfisherOptionsInfoItem/originalCache(_:)``
  105. /// is contained in the options, it will be preferred for storing and retrieving original images.
  106. /// If there is no ``KingfisherOptionsInfoItem/originalCache(_:)`` in the options,
  107. /// ``KingfisherOptionsInfoItem/targetCache(_:)`` will be used to store original images.
  108. ///
  109. /// When using ``KingfisherManager`` to download and store an image, if
  110. /// ``KingfisherOptionsInfoItem/cacheOriginalImage`` is applied in the option, the original image will be stored to
  111. /// the `cache` you pass as parameter in this method. At the same time, if a requested final image (with processor
  112. /// applied) cannot be found in the cache defined by ``KingfisherOptionsInfoItem/targetCache(_:)``, Kingfisher
  113. /// will try to search the original image to check whether it is already there. If found, it will be used and
  114. /// applied with the given processor. It is an optimization for not downloading the same image for multiple times.
  115. ///
  116. public func originalCache(_ cache: ImageCache) -> Self {
  117. options.originalCache = cache
  118. return self
  119. }
  120. /// Sets the downloader to be used for the image download task.
  121. ///
  122. /// - Parameter downloader: The `ImageDownloader` instance to use for downloading.
  123. /// - Returns: A `Self` value with the changes applied.
  124. ///
  125. /// Kingfisher will utilize the specified ``ImageDownloader`` instance to download requested images.
  126. ///
  127. public func downloader(_ downloader: ImageDownloader) -> Self {
  128. options.downloader = downloader
  129. return self
  130. }
  131. /// Sets the download priority for the image task.
  132. ///
  133. /// - Parameter priority: The download priority of the image download task.
  134. /// - Returns: A `Self` value with changes applied.
  135. ///
  136. /// The `priority` value will be configured as the priority of the image download task. Valid values range between
  137. /// 0.0 and 1.0. You can select a value from `URLSessionTask.defaultPriority`, `URLSessionTask.lowPriority`,
  138. /// or `URLSessionTask.highPriority`. If this option is not set, the default value
  139. /// (`URLSessionTask.defaultPriority`) will be used.
  140. ///
  141. public func downloadPriority(_ priority: Float) -> Self {
  142. options.downloadPriority = priority
  143. return self
  144. }
  145. /// Sets whether Kingfisher should ignore the cache and attempt to initiate a download task for the image source.
  146. ///
  147. /// - Parameter enabled: Enable force refresh or not.
  148. /// - Returns: A `Self` value with the changes applied.
  149. ///
  150. public func forceRefresh(_ enabled: Bool = true) -> Self {
  151. options.forceRefresh = enabled
  152. return self
  153. }
  154. /// Sets whether Kingfisher should attempt to retrieve the image from the memory cache first. If the image is not
  155. /// found in the memory cache, it bypasses the disk cache and initiates a download task for the image source.
  156. ///
  157. /// - Parameter enabled: Enable memory-only cache searching or not.
  158. /// - Returns: A `Self` value with the changes applied.
  159. ///
  160. /// This option is useful when you want to display a changeable image with the same URL during the same app session
  161. /// while avoiding multiple downloads of the same image.
  162. ///
  163. public func fromMemoryCacheOrRefresh(_ enabled: Bool = true) -> Self {
  164. options.fromMemoryCacheOrRefresh = enabled
  165. return self
  166. }
  167. /// Sets whether the image should be cached only in memory and not on disk.
  168. ///
  169. /// - Parameter enabled: Enable memory-only caching for the image or not.
  170. /// - Returns: A `Self` value with the changes applied.
  171. ///
  172. public func cacheMemoryOnly(_ enabled: Bool = true) -> Self {
  173. options.cacheMemoryOnly = enabled
  174. return self
  175. }
  176. /// Sets whether Kingfisher should wait for caching operations to be completed before invoking the `onSuccess`
  177. /// or `onFailure` block.
  178. ///
  179. /// - Parameter enabled: Enable waiting for caching operations or not.
  180. /// - Returns: A `Self` value with the changes applied.
  181. ///
  182. public func waitForCache(_ enabled: Bool = true) -> Self {
  183. options.waitForCache = enabled
  184. return self
  185. }
  186. /// Sets whether Kingfisher should exclusively attempt to retrieve the image from the cache and not from the network.
  187. ///
  188. /// - Parameter enabled: Enable cache-only image retrieval or not.
  189. /// - Returns: A `Self` value with the changes applied.
  190. ///
  191. /// If the image is not found in the cache, the image retrieval will fail with a
  192. /// ``KingfisherError/CacheErrorReason/imageNotExisting(key:)`` error.
  193. ///
  194. public func onlyFromCache(_ enabled: Bool = true) -> Self {
  195. options.onlyFromCache = enabled
  196. return self
  197. }
  198. /// Sets whether the image should be decoded on a background thread before usage.
  199. ///
  200. /// - Parameter enabled: Enable background image decoding or not.
  201. /// - Returns: A `Self` value with the changes applied.
  202. ///
  203. /// When set to `true`, the downloaded image data will be decoded and undergo off-screen rendering to extract pixel
  204. /// information in the background. This can enhance display speed but may consume additional time and memory for
  205. /// image preparation before usage.
  206. ///
  207. public func backgroundDecode(_ enabled: Bool = true) -> Self {
  208. options.backgroundDecode = enabled
  209. return self
  210. }
  211. /// Sets the callback queue used as the target queue for dispatching callbacks when retrieving images from the
  212. /// cache. If not set, Kingfisher will use the main queue for callbacks.
  213. ///
  214. /// - Parameter queue: The target queue on which cache retrieval callbacks will be invoked.
  215. /// - Returns: A `Self` value with the changes applied.
  216. ///
  217. /// - Note: This option does not impact callbacks for UI-related extension methods or ``KFImage`` result handlers.
  218. /// Callbacks for those methods will always be executed on the main queue.
  219. ///
  220. public func callbackQueue(_ queue: CallbackQueue) -> Self {
  221. options.callbackQueue = queue
  222. return self
  223. }
  224. /// Sets the scale factor value used when converting retrieved data to an image.
  225. ///
  226. /// - Parameter factor: The scale factor value to use.
  227. /// - Returns: A `Self` value with the changes applied.
  228. ///
  229. /// Specify the image scale factor, which may differ from your screen's scale. This is particularly important when
  230. /// working with 2x or 3x retina images. Failure to set the correct scale factor may result in Kingfisher
  231. /// converting the data to an image object with a `scale` of 1.0.
  232. ///
  233. public func scaleFactor(_ factor: CGFloat) -> Self {
  234. options.scaleFactor = factor
  235. return self
  236. }
  237. /// Sets whether the original image should be cached, even when the original image has been processed by other ``ImageProcessor``s.
  238. ///
  239. /// - Parameter enabled: Whether to cache the original image.
  240. /// - Returns: A `Self` value with the changes applied.
  241. ///
  242. /// When this option is set, and an ``ImageProcessor`` is used, Kingfisher will attempt to cache both the final
  243. /// processed image and the original image. This ensures that the original image can be reused when another
  244. /// processor is applied to the same resource, without the need for redownloading. You can use
  245. /// ``KingfisherOptionsInfoItem/originalCache(_:)`` to specify a cache for the original images.
  246. ///
  247. /// - Note: The original image will be cached only in disk storage.
  248. ///
  249. public func cacheOriginalImage(_ enabled: Bool = true) -> Self {
  250. options.cacheOriginalImage = enabled
  251. return self
  252. }
  253. /// Sets writing options for an original image on its initial write to disk storage.
  254. ///
  255. /// - Parameter writingOptions: Options that control the data writing operation to disk storage.
  256. /// - Returns: A `Self` value with the changes applied.
  257. ///
  258. /// If these options are set, they will be applied to the storage operation for new files. This can be useful if
  259. /// you want to implement features such as file encryption on the initial write, for example,
  260. /// using `[.completeFileProtection]`.
  261. ///
  262. public func diskStoreWriteOptions(_ writingOptions: Data.WritingOptions) -> Self {
  263. options.diskStoreWriteOptions = writingOptions
  264. return self
  265. }
  266. /// Sets whether disk storage loading should occur in the same calling queue.
  267. ///
  268. /// - Parameter enabled: Whether disk storage loading should happen in the same calling queue.
  269. /// - Returns: A `Self` value with the changes applied.
  270. ///
  271. /// By default, disk storage file loading operates in its own queue with asynchronous dispatch behavior. While this
  272. /// provides better non-blocking disk loading performance, it can result in flickering when reloading an image
  273. /// from disk if the image view already has an image set.
  274. ///
  275. /// Enabling this option prevents flickering by performing all loading in the same queue (typically the UI queue if
  276. /// you are using Kingfisher's extension methods to set an image). However, this may come at the cost of loading
  277. /// performance.
  278. ///
  279. public func loadDiskFileSynchronously(_ enabled: Bool = true) -> Self {
  280. options.loadDiskFileSynchronously = enabled
  281. return self
  282. }
  283. /// Sets the queue on which image processing should occur.
  284. ///
  285. /// - Parameter queue: The queue on which image processing should take place.
  286. /// - Returns: A `Self` value with the changes applied.
  287. ///
  288. /// By default, Kingfisher employs a pre-defined serial queue for image processing. Use this option to modify this
  289. /// behavior. For example, specify `.mainCurrentOrAsync` to process the image on the main queue, which can prevent
  290. /// potential flickering but may lead to UI blocking if the processor requires substantial time to execute.
  291. ///
  292. public func processingQueue(_ queue: CallbackQueue?) -> Self {
  293. options.processingQueue = queue
  294. return self
  295. }
  296. /// Sets the alternative sources to be used when loading the original input `Source` fails.
  297. ///
  298. /// - Parameter sources: The alternative sources to be used.
  299. /// - Returns: A `Self` value with the changes applied.
  300. ///
  301. /// The values in the `sources` array will be employed to initiate a new image loading task if the previous task
  302. /// fails due to an error. The image source loading process will terminate as soon as one of the alternative
  303. /// sources is successfully loaded. If all `sources` are used but loading still fails,
  304. /// a ``KingfisherError/ImageSettingErrorReason/alternativeSourcesExhausted(_:)`` error will be thrown in the
  305. /// `catch` block.
  306. ///
  307. /// This feature is valuable when implementing a fallback solution for setting images.
  308. ///
  309. /// - Note: User cancellation or calling on ``DownloadTask/cancel()`` on ``DownloadTask`` will not trigger the
  310. /// loading of alternative sources.
  311. ///
  312. public func alternativeSources(_ sources: [Source]?) -> Self {
  313. options.alternativeSources = sources
  314. return self
  315. }
  316. /// Sets a retry strategy to be used when issues arise during image retrieval.
  317. ///
  318. /// - Parameter strategy: The provided strategy that defines how retry attempts should occur.
  319. /// - Returns: A `Self` value with the changes applied.
  320. ///
  321. public func retry(_ strategy: RetryStrategy?) -> Self {
  322. options.retryStrategy = strategy
  323. return self
  324. }
  325. /// Sets a retry strategy with a maximum retry count and retry interval.
  326. ///
  327. /// - Parameters:
  328. /// - maxCount: The maximum number of retry attempts before the retry stops.
  329. /// - interval: The time interval between each retry attempt.
  330. /// - Returns: A `Self` value with the changes applied.
  331. ///
  332. /// This defines a straightforward retry strategy that retries a failing request for a specified number of times
  333. /// with a designated time interval between each attempt. For example, `.retry(maxCount: 3, interval: .second(3))`
  334. /// indicates a maximum of three retry attempts, with a 3-second pause between each retry if the previous attempt
  335. /// fails.
  336. ///
  337. public func retry(maxCount: Int, interval: DelayRetryStrategy.Interval = .seconds(3)) -> Self {
  338. let strategy = DelayRetryStrategy(maxRetryCount: maxCount, retryInterval: interval)
  339. options.retryStrategy = strategy
  340. return self
  341. }
  342. /// Sets the `Source` to be loaded when the user enables Low Data Mode and the original source fails with an
  343. /// `NSURLErrorNetworkUnavailableReason.constrained` error.
  344. ///
  345. /// - Parameter source: The `Source` to be loaded under low data mode.
  346. /// - Returns: A `Self` value with the changes applied.
  347. ///
  348. /// When this option is set, the `allowsConstrainedNetworkAccess` property of the request for the original source
  349. /// will be set to `false`, and the specified ``Source`` will be used to retrieve the image in low data mode.
  350. /// Typically, you can provide a low-resolution version of your image or a local image provider to display a
  351. /// placeholder.
  352. ///
  353. /// If this option is not set or the `source` is `nil`, the device's Low Data Mode setting will be disregarded,
  354. /// and the original source will be loaded following the system's default behavior in a regular manner.
  355. ///
  356. public func lowDataModeSource(_ source: Source?) -> Self {
  357. options.lowDataModeSource = source
  358. return self
  359. }
  360. /// Sets whether the image setting for an image view should include a transition even when the image is retrieved
  361. /// from the cache.
  362. ///
  363. /// - Parameter enabled: Enable the use of a transition or not.
  364. /// - Returns: A `Self` value with the changes applied.
  365. ///
  366. public func forceTransition(_ enabled: Bool = true) -> Self {
  367. options.forceTransition = enabled
  368. return self
  369. }
  370. /// Sets the image to be used in the event of a failure during image retrieval.
  371. ///
  372. /// - Parameter image: The image to be used when an error occurs.
  373. /// - Returns: A `Self` value with the changes applied.
  374. ///
  375. /// If this option is set and an image retrieval error occurs, Kingfisher will use the provided image (or an empty
  376. /// image) in place of the requested one. This is useful when you do not want to display a placeholder during the
  377. /// loading process but prefer to use a default image when requests fail.
  378. ///
  379. public func onFailureImage(_ image: KFCrossPlatformImage?) -> Self {
  380. options.onFailureImage = .some(image)
  381. return self
  382. }
  383. }
  384. // MARK: - Request Modifier
  385. extension KFOptionSetter {
  386. /// Sets an ``ImageDownloadRequestModifier`` to alter the image download request before it is sent.
  387. ///
  388. /// - Parameter modifier: The modifier to be used for changing the request before it is sent.
  389. /// - Returns: A `Self` value with the changes applied.
  390. ///
  391. /// This is your last opportunity to modify the image download request. You can use this for customization
  392. /// purposes, such as adding an authentication token to the header, implementing basic HTTP authentication,
  393. /// or URL mapping.
  394. public func requestModifier(_ modifier: AsyncImageDownloadRequestModifier) -> Self {
  395. options.requestModifier = modifier
  396. return self
  397. }
  398. /// Sets a block to modify the image download request before it is sent.
  399. ///
  400. /// - Parameter modifyBlock: The modifying block that will be called to change the request before it is sent.
  401. /// - Returns: A `Self` value with the changes applied.
  402. ///
  403. /// This is your last opportunity to modify the image download request. You can use this for customization purposes,
  404. /// such as adding an authentication token to the header, implementing basic HTTP authentication, or URL mapping.
  405. ///
  406. public func requestModifier(_ modifyBlock: @escaping @Sendable (inout URLRequest) -> Void) -> Self {
  407. options.requestModifier = AnyModifier { r -> URLRequest? in
  408. var request = r
  409. modifyBlock(&request)
  410. return request
  411. }
  412. return self
  413. }
  414. }
  415. // MARK: - Redirect Handler
  416. extension KFOptionSetter {
  417. /// Sets an `ImageDownloadRedirectHandler` to modify the image download request during redirection.
  418. ///
  419. /// - Parameter handler: The handler to be used for redirection.
  420. /// - Returns: A `Self` value with the changes applied.
  421. ///
  422. /// This provides an opportunity to modify the image download request during redirection. You can use this for
  423. /// customization purposes, such as adding an authentication token to the header, implementing basic HTTP
  424. /// authentication, or URL mapping. By default, the original redirection request will be sent without any
  425. /// modification.
  426. ///
  427. public func redirectHandler(_ handler: ImageDownloadRedirectHandler) -> Self {
  428. options.redirectHandler = handler
  429. return self
  430. }
  431. /// Sets a block to modify the image download request during redirection.
  432. ///
  433. /// - Parameter block: The block to be used for redirection.
  434. /// - Returns: A `Self` value with the changes applied.
  435. ///
  436. /// This provides an opportunity to modify the image download request during redirection. You can use this for
  437. /// customization purposes, such as adding an authentication token to the header, implementing basic HTTP
  438. /// authentication, or URL mapping. By default, the original redirection request will be sent without any
  439. /// modification.
  440. ///
  441. public func redirectHandler(_ block: @escaping @Sendable (KF.RedirectPayload) -> Void) -> Self {
  442. let redirectHandler = AnyRedirectHandler { (task, response, request, handler) in
  443. let payload = KF.RedirectPayload(
  444. task: task, response: response, newRequest: request, completionHandler: handler
  445. )
  446. block(payload)
  447. }
  448. options.redirectHandler = redirectHandler
  449. return self
  450. }
  451. }
  452. // MARK: - Processor
  453. extension KFOptionSetter {
  454. /// Sets an image processor for the image task, replacing the current image processor settings.
  455. ///
  456. /// - Parameter processor: The processor to use for processing the image after it is downloaded.
  457. /// - Returns: A `Self` value with the changes applied.
  458. ///
  459. /// - Note: To append a processor to the current ones instead of replacing them all, use ``appendProcessor(_:)``.
  460. ///
  461. public func setProcessor(_ processor: ImageProcessor) -> Self {
  462. options.processor = processor
  463. return self
  464. }
  465. /// Sets an array of image processors for the image task, replacing the current image processor settings.
  466. ///
  467. /// - Parameter processors: An array of processors. The processors in this array will be concatenated one by one to
  468. /// form a processor pipeline.
  469. /// - Returns: A `Self` value with the changes applied.
  470. ///
  471. /// - Note: To append processors to the current ones instead of replacing them all, concatenate them using the
  472. /// `|>` operator, and then use ``KFOptionSetter/appendProcessor(_:)``.
  473. ///
  474. public func setProcessors(_ processors: [ImageProcessor]) -> Self {
  475. switch processors.count {
  476. case 0:
  477. options.processor = DefaultImageProcessor.default
  478. case 1...:
  479. options.processor = processors.dropFirst().reduce(processors[0]) { $0 |> $1 }
  480. default:
  481. assertionFailure("Never happen")
  482. }
  483. return self
  484. }
  485. /// Appends a processor to the current set of processors.
  486. ///
  487. /// - Parameter processor: The processor to append to the current processor settings.
  488. /// - Returns: A `Self` value with the changes applied.
  489. ///
  490. public func appendProcessor(_ processor: ImageProcessor) -> Self {
  491. options.processor = options.processor |> processor
  492. return self
  493. }
  494. /// Appends a ``RoundCornerImageProcessor`` to the current set of processors.
  495. ///
  496. /// - Parameters:
  497. /// - radius: The radius to apply during processing. Specify a certain point value with `.point`, or a fraction
  498. /// of the target image with `.widthFraction` or `.heightFraction`. For example, with a square image where width
  499. /// and height are equal, `.widthFraction(0.5)` means using half of the length of the size to make the final
  500. /// image round.
  501. /// - targetSize: The target size for the output image. If `nil`, the image will retain its original size after
  502. /// processing.
  503. /// - corners: The target corners to round.
  504. /// - backgroundColor: The background color of the output image. If `nil`, a transparent background will be used.
  505. /// - Returns: A `Self` value with the changes applied.
  506. ///
  507. public func roundCorner(
  508. radius: Radius,
  509. targetSize: CGSize? = nil,
  510. roundingCorners corners: RectCorner = .all,
  511. backgroundColor: KFCrossPlatformColor? = nil
  512. ) -> Self
  513. {
  514. let processor = RoundCornerImageProcessor(
  515. radius: radius,
  516. targetSize: targetSize,
  517. roundingCorners: corners,
  518. backgroundColor: backgroundColor
  519. )
  520. return appendProcessor(processor)
  521. }
  522. /// Appends a ``BlurImageProcessor`` to the current set of processors.
  523. ///
  524. /// - Parameter radius: The blur radius for simulating Gaussian blur.
  525. /// - Returns: A `Self` value with the changes applied.
  526. ///
  527. public func blur(radius: CGFloat) -> Self {
  528. appendProcessor(
  529. BlurImageProcessor(blurRadius: radius)
  530. )
  531. }
  532. /// Appends an ``OverlayImageProcessor`` to the current set of processors.
  533. ///
  534. /// - Parameters:
  535. /// - color: The overlay color to be used when overlaying the input image.
  536. /// - fraction: The fraction to be used when overlaying the color onto the image.
  537. /// - Returns: A `Self` value with the changes applied.
  538. ///
  539. public func overlay(color: KFCrossPlatformColor, fraction: CGFloat = 0.5) -> Self {
  540. appendProcessor(
  541. OverlayImageProcessor(overlay: color, fraction: fraction)
  542. )
  543. }
  544. /// Appends a ``TintImageProcessor`` to the current set of processors.
  545. ///
  546. /// - Parameter color: The tint color to be used for tinting the input image.
  547. /// - Returns: A `Self` value with the changes applied.
  548. ///
  549. public func tint(color: KFCrossPlatformColor) -> Self {
  550. appendProcessor(
  551. TintImageProcessor(tint: color)
  552. )
  553. }
  554. /// Appends a ``BlackWhiteProcessor`` to the current set of processors.
  555. ///
  556. /// - Returns: A `Self` value with the changes applied.
  557. ///
  558. public func blackWhite() -> Self {
  559. appendProcessor(
  560. BlackWhiteProcessor()
  561. )
  562. }
  563. /// Appends a ``CroppingImageProcessor`` to the current set of processors.
  564. ///
  565. /// - Parameters:
  566. /// - size: The target size for the output image.
  567. /// - anchor: The anchor point from which the output size should be calculated. The anchor point is represented
  568. /// by two values between 0.0 and 1.0, indicating a relative point in the current image. See
  569. /// ``CroppingImageProcessor/init(size:anchor:)`` for more details.
  570. /// - Returns: A `Self` value with the changes applied.
  571. ///
  572. public func cropping(size: CGSize, anchor: CGPoint = .init(x: 0.5, y: 0.5)) -> Self {
  573. appendProcessor(
  574. CroppingImageProcessor(size: size, anchor: anchor)
  575. )
  576. }
  577. /// Appends a ``DownsamplingImageProcessor`` to the current set of processors.
  578. ///
  579. /// Compared to the ``ResizingImageProcessor``, the ``DownsamplingImageProcessor`` doesn't render the original
  580. /// images and then resize them. Instead, it directly downsamples the input data to a thumbnail image, making it
  581. /// more efficient than the ``ResizingImageProcessor``. It is recommended to use the ``DownsamplingImageProcessor``
  582. /// whenever possible instead of the ``ResizingImageProcessor``.
  583. ///
  584. /// - Parameter size: The target size for the output image. It should be smaller than the size of the input image. If it is larger, the resulting image will be the same size as the input data without downsampling.
  585. /// - Returns: A `Self` value with the changes applied.
  586. ///
  587. /// - Note: Only CG-based images are supported, and animated images (e.g., GIF) are not supported.
  588. ///
  589. public func downsampling(size: CGSize) -> Self {
  590. let processor = DownsamplingImageProcessor(size: size)
  591. if options.processor == DefaultImageProcessor.default {
  592. return setProcessor(processor)
  593. } else {
  594. return appendProcessor(processor)
  595. }
  596. }
  597. /// Appends a ``ResizingImageProcessor`` to the current set of processors.
  598. ///
  599. /// If you need to resize a data-represented image to a smaller size, it is recommended to use the
  600. /// ``DownsamplingImageProcessor`` instead, which is more efficient and uses less memory.
  601. ///
  602. /// - Parameters:
  603. /// - referenceSize: The reference size for the resizing operation in points.
  604. /// - mode: The target content mode for the output image. The default is `.none`.
  605. /// - Returns: A `Self` value with the changes applied.
  606. ///
  607. public func resizing(referenceSize: CGSize, mode: ContentMode = .none) -> Self {
  608. appendProcessor(
  609. ResizingImageProcessor(referenceSize: referenceSize, mode: mode)
  610. )
  611. }
  612. }
  613. // MARK: - Cache Serializer
  614. extension KFOptionSetter {
  615. /// Uses a specified ``CacheSerializer`` to convert data to an image object for retrieval from the disk cache or
  616. /// vice versa for storage to the disk cache.
  617. ///
  618. /// - Parameter cacheSerializer: The ``CacheSerializer`` to be used.
  619. /// - Returns: A `Self` value with the changes applied.
  620. ///
  621. public func serialize(by cacheSerializer: CacheSerializer) -> Self {
  622. options.cacheSerializer = cacheSerializer
  623. return self
  624. }
  625. /// Uses a specified format to serialize the image data to disk. It converts the image object to the given data
  626. /// format.
  627. ///
  628. /// - Parameters:
  629. /// - format: The desired data encoding format when storing the image on disk.
  630. /// - jpegCompressionQuality: If the format is ``ImageFormat/JPEG``, it specifies the compression quality when
  631. /// converting the image to JPEG data. Otherwise, it is ignored.
  632. /// - Returns: A `Self` value with the changes applied.
  633. ///
  634. public func serialize(as format: ImageFormat, jpegCompressionQuality: CGFloat? = nil) -> Self {
  635. let cacheSerializer: FormatIndicatedCacheSerializer
  636. switch format {
  637. case .JPEG:
  638. cacheSerializer = .jpeg(compressionQuality: jpegCompressionQuality ?? 1.0)
  639. case .PNG:
  640. cacheSerializer = .png
  641. case .GIF:
  642. cacheSerializer = .gif
  643. case .unknown:
  644. cacheSerializer = .png
  645. }
  646. options.cacheSerializer = cacheSerializer
  647. return self
  648. }
  649. }
  650. // MARK: - Image Modifier
  651. extension KFOptionSetter {
  652. /// Sets an ``ImageModifier`` for the image task. Use this to modify the fetched image object's properties if needed.
  653. ///
  654. /// If the image was fetched directly from the downloader, the modifier will run directly after the
  655. /// ``ImageProcessor``. If the image is being fetched from a cache, the modifier will run after the
  656. /// ``CacheSerializer``.
  657. ///
  658. /// - Parameter modifier: The ``ImageModifier`` to be used for modifying the image object.
  659. /// - Returns: A `Self` value with the changes applied.
  660. ///
  661. public func imageModifier(_ modifier: ImageModifier?) -> Self {
  662. options.imageModifier = modifier
  663. return self
  664. }
  665. /// Sets a block to modify the image object. Use this to modify the fetched image object's properties if needed.
  666. ///
  667. /// If the image was fetched directly from the downloader, the modifier block will run directly after the
  668. /// ``ImageProcessor``. If the image is being fetched from a cache, the modifier will run after the
  669. /// ``CacheSerializer``.
  670. ///
  671. /// - Parameter block: The block used to modify the image object.
  672. /// - Returns: A `Self` value with the changes applied.
  673. ///
  674. public func imageModifier(_ block: @escaping @Sendable (inout KFCrossPlatformImage) throws -> Void) -> Self {
  675. let modifier = AnyImageModifier { image -> KFCrossPlatformImage in
  676. var image = image
  677. try block(&image)
  678. return image
  679. }
  680. options.imageModifier = modifier
  681. return self
  682. }
  683. }
  684. // MARK: - Cache Expiration
  685. extension KFOptionSetter {
  686. /// Sets the expiration setting for the memory cache of this image task.
  687. ///
  688. /// By default, the underlying ``MemoryStorage/Backend`` uses the expiration in its configuration for all items.
  689. /// If set, the ``MemoryStorage/Backend`` will use this value to overwrite the configuration setting for this
  690. /// caching item.
  691. ///
  692. /// - Parameter expiration: The expiration setting used in cache storage.
  693. /// - Returns: A `Self` value with the changes applied.
  694. ///
  695. public func memoryCacheExpiration(_ expiration: StorageExpiration?) -> Self {
  696. options.memoryCacheExpiration = expiration
  697. return self
  698. }
  699. /// Sets the expiration extending setting for the memory cache. The item expiration time will be incremented by this
  700. /// value after access.
  701. ///
  702. /// By default, the underlying ``MemoryStorage/Backend`` uses the initial cache expiration as the extending value:
  703. /// ``ExpirationExtending/cacheTime``.
  704. ///
  705. /// To disable the extending option entirely, set `.none` to it.
  706. ///
  707. /// - Parameter extending: The expiration extending setting used in cache storage.
  708. /// - Returns: A `Self` value with the changes applied.
  709. ///
  710. public func memoryCacheAccessExtending(_ extending: ExpirationExtending) -> Self {
  711. options.memoryCacheAccessExtendingExpiration = extending
  712. return self
  713. }
  714. /// Sets the expiration setting for the disk cache of this image task.
  715. ///
  716. /// By default, the underlying ``DiskStorage/Backend`` uses the expiration in its configuration for all items.
  717. /// If set, the ``DiskStorage/Backend`` will use this value to overwrite the configuration setting for this caching
  718. /// item.
  719. ///
  720. /// - Parameter expiration: The expiration setting used in cache storage.
  721. /// - Returns: A `Self` value with the changes applied.
  722. ///
  723. public func diskCacheExpiration(_ expiration: StorageExpiration?) -> Self {
  724. options.diskCacheExpiration = expiration
  725. return self
  726. }
  727. /// Sets the expiration extending setting for the disk cache. The item expiration time will be incremented by this
  728. /// value after access.
  729. ///
  730. /// By default, the underlying ``DiskStorage/Backend`` uses the initial cache expiration as the extending
  731. /// value: ``ExpirationExtending/cacheTime``.
  732. ///
  733. /// To disable the extending option entirely, set `.none` to it.
  734. ///
  735. /// - Parameter extending: The expiration extending setting used in cache storage.
  736. /// - Returns: A `Self` value with the changes applied.
  737. ///
  738. public func diskCacheAccessExtending(_ extending: ExpirationExtending) -> Self {
  739. options.diskCacheAccessExtendingExpiration = extending
  740. return self
  741. }
  742. }