KingfisherTestHelper.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. //
  2. // KingfisherTestHelper.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 15/4/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. @testable import Kingfisher
  28. import CoreGraphics
  29. let testImageString =
  30. "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAD8GlDQ1BJQ0MgUHJvZmlsZQAAOI2NVd1v21QUP4lvXKQWP6Cxjg4Vi69VU1u5GxqtxgZJk6XpQhq5zdgqpMl1bhpT1za2021V" +
  31. "n/YCbwz4A4CyBx6QeEIaDMT2su0BtElTQRXVJKQ9dNpAaJP2gqpwrq9Tu13GuJGvfznndz7v0TVAx1ea45hJGWDe8l01n5GPn5iWO1YhCc9BJ/RAp6Z7TrpcLgIuxoVH1sNfIcHeNwfa6/9z" +
  32. "dVappwMknkJsVz19HvFpgJSpO64PIN5G+fAp30Hc8TziHS4miFhheJbjLMMzHB8POFPqKGKWi6TXtSriJcT9MzH5bAzzHIK1I08t6hq6zHpRdu2aYdJYuk9Q/881bzZa8Xrx6fLmJo/iu4/V" +
  33. "XnfH1BB/rmu5ScQvI77m+BkmfxXxvcZcJY14L0DymZp7pML5yTcW61PvIN6JuGr4halQvmjNlCa4bXJ5zj6qhpxrujeKPYMXEd+q00KR5yNAlWZzrF+Ie+uNsdC/MO4tTOZafhbroyXuR3Df" +
  34. "08bLiHsQf+ja6gTPWVimZl7l/oUrjl8OcxDWLbNU5D6JRL2gxkDu16fGuC054OMhclsyXTOOFEL+kmMGs4i5kfNuQ62EnBuam8tzP+Q+tSqhz9SuqpZlvR1EfBiOJTSgYMMM7jpYsAEyqJCH" +
  35. "DL4dcFFTAwNMlFDUUpQYiadhDmXteeWAw3HEmA2s15k1RmnP4RHuhBybdBOF7MfnICmSQ2SYjIBM3iRvkcMki9IRcnDTthyLz2Ld2fTzPjTQK+Mdg8y5nkZfFO+se9LQr3/09xZr+5GcaSuf" +
  36. "eAfAww60mAPx+q8u/bAr8rFCLrx7s+vqEkw8qb+p26n11Aruq6m1iJH6PbWGv1VIY25mkNE8PkaQhxfLIF7DZXx80HD/A3l2jLclYs061xNpWCfoB6WHJTjbH0mV35Q/lRXlC+W8cndbl9t2" +
  37. "SfhU+Fb4UfhO+F74GWThknBZ+Em4InwjXIyd1ePnY/Psg3pb1TJNu15TMKWMtFt6ScpKL0ivSMXIn9QtDUlj0h7U7N48t3i8eC0GnMC91dX2sTivgloDTgUVeEGHLTizbf5Da9JLhkhh29QO" +
  38. "s1luMcScmBXTIIt7xRFxSBxnuJWfuAd1I7jntkyd/pgKaIwVr3MgmDo2q8x6IdB5QH162mcX7ajtnHGN2bov71OU1+U0fqqoXLD0wX5ZM005UHmySz3qLtDqILDvIL+iH6jB9y2x83ok898G" +
  39. "OPQX3lk3Itl0A+BrD6D7tUjWh3fis58BXDigN9yF8M5PJH4B8Gr79/F/XRm8m241mw/wvur4BGDj42bzn+Vmc+NL9L8GcMn8F1kAcXgSteGGAAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlU" +
  40. "WHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9" +
  41. "Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJo" +
  42. "dHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8" +
  43. "L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAKZklEQVR4Ae2ax28VyxLGywYMJuecgwgSIILIgg1pQRRJQrBkxZr9/RNYAhJiA0gEIbIE6JEzIggQIKLJOefod351+fzmzps5njke3wV2" +
  44. "SeM+Mx2qvq+qq3t6XNS1a9fyHz9+WE2V4poMHqcX11TPC3ctAWKippa1EVBTPS/cNT4C6oqJf7MsKiqKVVdeXh5bVx0V/woBcYCDYNVGpcAG2+hZlmW1EgAYrl+/ftnPnz+NTdenT5/s8+fP" +
  45. "sRgaN25sDRo0sLp161pxcbFfkFBdRFQLAQIO6G/fvtmHDx8cwMCBA61Pnz7WqVMna9GihQG2fv36Tsj79+/t5cuXdu/ePbt165ZdunTJGjVqZKWlpVZSUuJEQGTWkjkBeA1D8fKXL1+sd+/e" +
  46. "Nnr0aBs8eLADLqlfYqUNSq1evXru5Tp16nh0fP/+3cmiD6S9fv3azp07Z8eOHbNHjx45GZCFZBkNRR07dsws6wAe4wHfrVs3mzp1quH1Jk2aOHgig6iAIIU1pSJGIU9Ju48fPzoRZ86csT17" +
  47. "9tiLFy98LEjLKhoyIwCjAY7hs2fPtgkTJljLli09xAHJ/BdYvAjooFAnUTvyAO2IiocPHzoJu3fv9unDtMiChEwIwCPM39yrtc2ZM8dGjBhRARxCkDBggc1XihTGpz+55MCBA7ZlyxYnhRyi" +
  48. "8fONk6+uTi48/8rXoLI6jMM7Q4cOtUWLFnmJ5zBMniwEPHrpx4WnuVgdevToYW3atLGysjJ79eqVJ0kRVZmtUfVVIoCwx/NDhgxx8P369XMdGCvjo5SmfaaxGBdyO3fubK1bt7YbN24YqwfT" +
  49. "oVASCt4KA565iTHz5s3zbC9PAVC/CzVMJNFfY/GMyCLqhg0bZnPnznXg1ENSIVIQASjDMLIyCa9///5+L7AYyPpNyPKb56qTkXoWLFVHqed4nHEYD9IRSGAZZXmdNWuWL5PoKUQKmgIoe/Dg" +
  50. "gS1YsMCmTJnixikZUUeyun//vpcAYEODKC/wGzBctFeICzQlAKl7+/atPXnyxMnWpoh6xuKefMCe4erVq75EUpdGUhOAsWxa2rdvbwsXLrQOHTrY169fK7wDqPPnz9uyZcvs9u3bvi8ACBm7" +
  51. "YcOGDgwD6cPFNNK5JBsdLtozt69fv24HDx609evX27p162zcuHEOGPDooWzatKkTcfToUS/TEpB6J4hxZH3mfW4T5WRoSuBtDGcri5AgV61a5XmC+dqzZ08n5N27d/bmzRvf6AAeb0MQ22MA" +
  52. "IRcvXrQ1a9b4b8hG2BF2797dCWLeIwBmtzljxgzbsGGDL8Ui1BtU8icVAQDFY2T7AQMGeGiz+cEbGEI9Xn327Jk1a9bMM3aXLl382fLly313ePfu3bwmQQAXUwyCIZyxIQnSAAfRiKKRDRc7" +
  53. "zu3bt3vClEPyKvpdmYoAFON9Eg/Gse1FWVgwGA+pxHhWC8jhRUigwv0YC4CENsQFsz/TjvHCQh/ad89FxsSJE23Hjh2poiDVKqCwwzj29ygOEkA9iYn3AKaC6kQEwBGBpH/w4jl9NL/DgNu2" +
  54. "beuRgB6NTUk/6pgKaSUxARjPzmvs2LGeiGRsUCHGcImoYB2/ARQGlaZN+W/g6IgahxWBPKNpGR476j4VAbyd9erVy1cAPBclzF0yd7t27WKJiOoX9wygACZB/mf/fl8OiZCwYA/TC/ueP39e" +
  55. "sSqF24Xv/3+kcIvf92K8VatWnvyCYUidVgBeVlgFmPfqEzNk4seMw9Q6fvy4nTp1ynNJMI8o6pgGEI9EkRSlMBEBKJDH2ZVFCW1Y3o4cOVJhRFS7Qp5BAICaN29up0+f9uQbBEg9F05gOUXi" +
  56. "pmFYfyIC6AQBzDFePMICeHICyx8rA4JBWQo6EPYW7A6D46uOZ0QekikBKIAA2GWnFlTOb9Uz95Cgd/xBBn/Qg4dZDknGwSnI8NhAG+xjhQrXx5mQOAIYkHkntqMGDBITVV/VZ9Id5V3ppo2W" +
  57. "2yT6EhOAV4mCKOUoQnHU9EhiRNI2gESPwjyqH/YpX0XVh58lIgDFsMrmJmr3p3q2pPyOIymsPM09wMkzgEcPDkEXQkk9gn3YGaz3ipg/iQigLwTw/q8kp/FQjAHMTyVJCJBBapdFiWeDJ8wa" +
  58. "UzYAmhyB8DuJJGoFQM0rXobiwLFE8uGjOiKAMQHF+Ngi7wNSEUCE4H0kUwIYUKBZhjjwQEHQCJSzSvDOTpukBjB2ZSIPs/wNHz7cT4iIBtlEf/Q9ffrUL+6TOiFRBDCgwo9DCt7LFRHUYciv" +
  59. "n3+f2g4aNMhGjhzpn7iySopML3TOnDnTX3jC5GMD9tCGXSg7wmohgJ3YyZMnPReEw7Co+O+TIrbKHH5wZlCWO7qGBAyGpKDHMDpO1BYdJD3OEIisadOm+TacaIsai43YnTt3fNucOQEYq7Dm" +
  60. "hYdpECZBcxHwS5Ys8a9DkMBBBgYJWBxwnqsN7TlXBDznD4sXL/aXnTAwdGIHmzCO4JDg1PQHef6kPhNkp8Uc50SIUCPryhuUMkjv55wY40VI43CTJKr9epRdhDHvFETb5MmT/eB1/PjxfvYI" +
  61. "eJGsvtxj05UrV2zt2rX+vSANAalOhDCAtzKUcQrL6yfzk+dBEuQl6iGCiJg+fbq/p1+4cMH27dvnXtNcpi9Ecmi6dOlSf6dnRYEEPoAgIlp6eAbQejn9bI05QxRxTJGkkjgJakCSIaD4SPn4" +
  62. "8WP3bphxjOTSnoGTYyKGwwoyOUdjAq9xiRLqOHOgLaSRTwAu8GobLItz4U/i4ygMwrEvjaQmQCF38+ZNO3TokBuN8WESMAISeI5HAHbixAnbuXOnL2NhT0IIxu/du9cuX75ccVwuMsOgiDIS" +
  63. "LEsfEQVJYVLDfaLuUxPAICiH7c2bN9vZs2cdYD7lqiNJ8ZEEwjRNZBT35AZyBO0Arn5qoxJSGYNEvD93SsTFQUha7zNeQQRgAAZi8NatW/0jZT6DUaRIIPzjhDEgAG/GCeOQ9WnDP05s27at" +
  64. "IkHG9cn3vCACGBCP4U2WObLvtWvXXE94aYxSDtA4oS6qHuBcJF2mFP8+s2nTpsQ64/SlWgXCgxByZGmmATJ//nxPYMxNjMRgyT9A/e+xqv9RBvtRAdlMB8Bz4su54MaNG31DxkeUQkJfCguO" +
  65. "AA2Acr4TkBRXrlzp/8HBcoTBzFMEQICghIicL9U9tlQf2rPOI+w/du3aZatXr/YPovo2ETtIgooqRYDGhwSM4U1sxYoV/lFz0qRJ/vWItV0AICXfksZ48jZRBHg8ztdfljrAE218dmNc2lZV" +
  66. "MiFAhgOUHHD48GFPUKNGjbIxY8Z4ksKjgCdv8DtKeM4YJEKWN/YRZbkcw0kzGyjq+T6AjizAY0Mm/yQVBKO5DliAMB369u3r3wY5UAEYkRAlAKQ/SyxE4XXeB9gRQoz2G3EERo1Z2bPMCZBC" +
  67. "QGIonoIMkqIiRG3iStrifaYB3hZhWQKX7symgAZUqRDFeIBzJQVAtlcCZbyk/aQ7TVltBMiIQo0vtJ/0Ji2jJ2PS3n9Au1oC/gAnVglCbQRUib4/oHNtBPwBTqwShGI2HTVZ/gvZ53KpZJXY" +
  68. "DwAAAABJRU5ErkJggg=="
  69. var testImage = KFCrossPlatformImage(data: testImageData)!
  70. let testImageData = Data(base64Encoded: testImageString)!
  71. let testImagePNGData = testImage.kf.pngRepresentation()!
  72. let testImageJEPGData = testImage.kf.jpegRepresentation(compressionQuality: 1.0)!
  73. let testImageGIFData = Data(fileName: "dancing-banana.gif")
  74. let testImageSingleFrameGIFData = Data(fileName: "single-frame.gif")
  75. let testKeys = [
  76. "http://stackoverflow.com/questions/11251340/convert-image-to-base64-string-in-ios-swift",
  77. "https://onevcat.com",
  78. "http://onevcat.com/content/images/2014/May/200.jpg",
  79. "http://onevcat.com/content/images/2014/May/200.jpg?fads#kj1asf"
  80. ]
  81. let testURLs = testKeys.map { URL(string: $0)! }
  82. func cleanDefaultCache() {
  83. let cache = KingfisherManager.shared.cache
  84. cache.clearMemoryCache()
  85. try? cache.diskStorage.removeAll()
  86. }
  87. func clearCaches(_ caches: [ImageCache]) {
  88. for c in caches {
  89. c.clearMemoryCache()
  90. try? c.diskStorage.removeAll(skipCreatingDirectory: true)
  91. }
  92. }
  93. func delay(_ time: Double, block: @escaping ()->()) {
  94. DispatchQueue.main.asyncAfter(deadline: .now() + time) { block() }
  95. }
  96. extension KFCrossPlatformImage {
  97. func renderEqual(to image: KFCrossPlatformImage, withinTolerance tolerance: UInt8 = 3) -> Bool {
  98. guard size == image.size else { return false }
  99. guard let imageData1 = kf.pngRepresentation(),
  100. let imageData2 = image.kf.pngRepresentation() else
  101. {
  102. return false
  103. }
  104. guard let unifiedImage1 = KFCrossPlatformImage(data: imageData1),
  105. let unifiedImage2 = KFCrossPlatformImage(data: imageData2) else
  106. {
  107. return false
  108. }
  109. guard let rendered1 = unifiedImage1.rendered(),
  110. let rendered2 = unifiedImage2.rendered() else
  111. {
  112. return false
  113. }
  114. guard let data1 = rendered1.kf.cgImage?.dataProvider?.data,
  115. let data2 = rendered2.kf.cgImage?.dataProvider?.data else
  116. {
  117. return false
  118. }
  119. let length1 = CFDataGetLength(data1)
  120. let length2 = CFDataGetLength(data2)
  121. guard length1 == length2 else { return false }
  122. let dataPtr1: UnsafePointer<UInt8> = CFDataGetBytePtr(data1)
  123. let dataPtr2: UnsafePointer<UInt8> = CFDataGetBytePtr(data2)
  124. for index in 0..<length1 {
  125. let byte1 = dataPtr1[index]
  126. let byte2 = dataPtr2[index]
  127. let delta = UInt8(abs(Int(byte1) - Int(byte2)))
  128. guard delta <= tolerance else {
  129. return false
  130. }
  131. }
  132. return true
  133. }
  134. func rendered() -> KFCrossPlatformImage? {
  135. // Ignore non CG images
  136. guard let cgImage = kf.cgImage else {
  137. return nil
  138. }
  139. var bitmapInfo = cgImage.bitmapInfo
  140. let colorSpace = CGColorSpaceCreateDeviceRGB()
  141. let alpha = (bitmapInfo.rawValue & CGBitmapInfo.alphaInfoMask.rawValue)
  142. let w = cgImage.width
  143. let h = cgImage.height
  144. let size = CGSize(width: w, height: h)
  145. if alpha == CGImageAlphaInfo.none.rawValue {
  146. bitmapInfo.remove(.alphaInfoMask)
  147. bitmapInfo = CGBitmapInfo(rawValue: bitmapInfo.rawValue | CGImageAlphaInfo.noneSkipFirst.rawValue)
  148. } else if !(alpha == CGImageAlphaInfo.noneSkipFirst.rawValue) ||
  149. !(alpha == CGImageAlphaInfo.noneSkipLast.rawValue)
  150. {
  151. bitmapInfo.remove(.alphaInfoMask)
  152. bitmapInfo = CGBitmapInfo(rawValue: bitmapInfo.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
  153. }
  154. // Render the image
  155. guard let context = CGContext(data: nil,
  156. width: w,
  157. height: h,
  158. bitsPerComponent: cgImage.bitsPerComponent,
  159. bytesPerRow: 0,
  160. space: colorSpace,
  161. bitmapInfo: bitmapInfo.rawValue) else
  162. {
  163. return nil
  164. }
  165. context.draw(cgImage, in: CGRect(origin: CGPoint.zero, size: size))
  166. #if os(macOS)
  167. return context.makeImage().flatMap { KFCrossPlatformImage(cgImage: $0, size: kf.size) }
  168. #else
  169. return context.makeImage().flatMap { KFCrossPlatformImage(cgImage: $0) }
  170. #endif
  171. }
  172. }
  173. #if os(iOS) || os(tvOS)
  174. import UIKit
  175. extension KFCrossPlatformImage {
  176. static func from(color: KFCrossPlatformColor, size: CGSize) -> KFCrossPlatformImage {
  177. let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
  178. UIGraphicsBeginImageContext(rect.size)
  179. let context = UIGraphicsGetCurrentContext()
  180. context!.setFillColor(color.cgColor)
  181. context!.fill(rect)
  182. let img = UIGraphicsGetImageFromCurrentImageContext()
  183. UIGraphicsEndImageContext()
  184. return img!
  185. }
  186. }
  187. #endif
  188. extension Data {
  189. init(fileName: String) {
  190. let comp = fileName.components(separatedBy: ".")
  191. guard comp.count == 2 else { fatalError() }
  192. self.init(named: comp[0], type: comp[1])
  193. }
  194. init(named name: String, type: String) {
  195. guard let path = Bundle.test.path(forResource: name, ofType: type) else {
  196. fatalError()
  197. }
  198. try! self.init(contentsOf: URL(fileURLWithPath: path))
  199. }
  200. }
  201. extension Bundle {
  202. static let test: Bundle = Bundle(for: ImageExtensionTests.self)
  203. }
  204. // Make tests happier with old Result type
  205. extension Result {
  206. var value: Success? {
  207. switch self {
  208. case .success(let success): return success
  209. case .failure: return nil
  210. }
  211. }
  212. var error: Failure? {
  213. switch self {
  214. case .success: return nil
  215. case .failure(let failure): return failure
  216. }
  217. }
  218. }