ImageProcessorTests.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. //
  2. // ImageProcessorTests.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 2016/08/30.
  6. //
  7. // Copyright (c) 2018 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 XCTest
  27. @testable import Kingfisher
  28. #if os(macOS)
  29. import AppKit
  30. #endif
  31. class ImageProcessorTests: XCTestCase {
  32. let imageNames = ["kingfisher.jpg", "onevcat.jpg", "unicorn.png"]
  33. var nonPNGIamgeNames: [String] {
  34. return imageNames.filter { !$0.contains(".png") }
  35. }
  36. func imageData(noAlpha: Bool = false) -> [Data] {
  37. return noAlpha ? nonPNGIamgeNames.map { Data(fileName: $0) } : imageNames.map { Data(fileName: $0) }
  38. }
  39. override func setUp() {
  40. super.setUp()
  41. // Put setup code here. This method is called before the invocation of each test method in the class.
  42. }
  43. override func tearDown() {
  44. // Put teardown code here. This method is called after the invocation of each test method in the class.
  45. super.tearDown()
  46. }
  47. func testRenderEqual() {
  48. let image1 = Image(data: testImageData as Data)!
  49. let image2 = Image(data: testImagePNGData)!
  50. XCTAssertTrue(image1.renderEqual(to: image2))
  51. }
  52. #if !os(macOS)
  53. func testBlendProcessor() {
  54. let p = BlendImageProcessor(blendMode: .darken, alpha: 1.0, backgroundColor: .lightGray)
  55. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.BlendImageProcessor(\(CGBlendMode.darken.rawValue),\(p.alpha))_#aaaaaaff")
  56. checkProcessor(p, with: "blend-\(CGBlendMode.darken.rawValue)")
  57. }
  58. #endif
  59. #if os(macOS)
  60. func testCompositingProcessor() {
  61. let p = CompositingImageProcessor(compositingOperation: .darken, alpha: 1.0, backgroundColor: .lightGray)
  62. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.CompositingImageProcessor(\(NSCompositingOperation.darken.rawValue),\(p.alpha))_\(Color.lightGray.hex)")
  63. checkProcessor(p, with: "compositing-\(NSCompositingOperation.darken.rawValue)")
  64. }
  65. #endif
  66. func testRoundCornerProcessor() {
  67. let p = RoundCornerImageProcessor(cornerRadius: 40)
  68. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.RoundCornerImageProcessor(40.0)")
  69. checkProcessor(p, with: "round-corner-40")
  70. }
  71. func testRoundCornerWithResizingProcessor() {
  72. let p = RoundCornerImageProcessor(cornerRadius: 60, targetSize: CGSize(width: 100, height: 100))
  73. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.RoundCornerImageProcessor(60.0_(100.0, 100.0))")
  74. checkProcessor(p, with: "round-corner-60-resize-100")
  75. }
  76. func testRoundCornerWithRectCornerProcessor() {
  77. let p1 = RoundCornerImageProcessor(cornerRadius: 40, roundingCorners: [.topLeft, .topRight])
  78. XCTAssertEqual(p1.identifier, "com.onevcat.Kingfisher.RoundCornerImageProcessor(40.0_corner(3))")
  79. checkProcessor(p1, with: "round-corner-40-corner-3")
  80. let p2 = RoundCornerImageProcessor(cornerRadius: 40, roundingCorners: [.bottomLeft, .bottomRight])
  81. XCTAssertEqual(p2.identifier, "com.onevcat.Kingfisher.RoundCornerImageProcessor(40.0_corner(12))")
  82. checkProcessor(p2, with: "round-corner-40-corner-12")
  83. let p3 = RoundCornerImageProcessor(cornerRadius: 40, roundingCorners: .all)
  84. XCTAssertEqual(p3.identifier, "com.onevcat.Kingfisher.RoundCornerImageProcessor(40.0)")
  85. checkProcessor(p3, with: "round-corner-40")
  86. }
  87. func testResizingProcessor() {
  88. let p = ResizingImageProcessor(referenceSize: CGSize(width: 120, height: 120))
  89. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.ResizingImageProcessor((120.0, 120.0))")
  90. checkProcessor(p, with: "resize-120")
  91. }
  92. func testResizingProcessorWithContentMode() {
  93. let p1 = ResizingImageProcessor(referenceSize: CGSize(width: 240, height: 60), mode: .aspectFill)
  94. XCTAssertEqual(p1.identifier, "com.onevcat.Kingfisher.ResizingImageProcessor((240.0, 60.0), aspectFill)")
  95. checkProcessor(p1, with: "resize-240-60-aspectFill")
  96. let p2 = ResizingImageProcessor(referenceSize: CGSize(width: 240, height: 60), mode: .aspectFit)
  97. XCTAssertEqual(p2.identifier, "com.onevcat.Kingfisher.ResizingImageProcessor((240.0, 60.0), aspectFit)")
  98. checkProcessor(p2, with: "resize-240-60-aspectFit")
  99. }
  100. func testBlurProcessor() {
  101. let p = BlurImageProcessor(blurRadius: 10)
  102. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.BlurImageProcessor(10.0)")
  103. // Alpha convolving would vary due to context. So we do not test blur for PNGs.
  104. // See results in Resource folder.
  105. checkProcessor(p, with: "blur-10", noAlpha: true)
  106. }
  107. func testOverlayProcessor() {
  108. let p1 = OverlayImageProcessor(overlay: .red)
  109. XCTAssertEqual(p1.identifier, "com.onevcat.Kingfisher.OverlayImageProcessor(\(Color.red.hex)_0.5)")
  110. checkProcessor(p1, with: "overlay-red")
  111. let p2 = OverlayImageProcessor(overlay: .red, fraction: 0.7)
  112. XCTAssertEqual(p2.identifier, "com.onevcat.Kingfisher.OverlayImageProcessor(\(Color.red.hex)_0.7)")
  113. checkProcessor(p2, with: "overlay-red-07")
  114. }
  115. func testTintProcessor() {
  116. let color = Color.yellow.withAlphaComponent(0.2)
  117. let p = TintImageProcessor(tint: color)
  118. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.TintImageProcessor(\(color.hex))")
  119. checkProcessor(p, with: "tint-yellow-02")
  120. }
  121. func testColorControlProcessor() {
  122. let p = ColorControlsProcessor(brightness: 0, contrast: 1.1, saturation: 1.2, inputEV: 0.7)
  123. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.ColorControlsProcessor(0.0_1.1_1.2_0.7)")
  124. checkProcessor(p, with: "color-control-b00-c11-s12-ev07")
  125. }
  126. func testBlackWhiteProcessor() {
  127. let p = BlackWhiteProcessor()
  128. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.BlackWhiteProcessor")
  129. checkProcessor(p, with: "b&w")
  130. }
  131. func testCompositionProcessor() {
  132. let p = BlurImageProcessor(blurRadius: 4) >> RoundCornerImageProcessor(cornerRadius: 60)
  133. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.BlurImageProcessor(4.0)|>com.onevcat.Kingfisher.RoundCornerImageProcessor(60.0)")
  134. // Alpha convolving would vary due to context. So we do not test blur for PNGs.
  135. // See results in Resource folder.
  136. checkProcessor(p, with: "blur-4-round-corner-60", noAlpha: true)
  137. }
  138. func testCIImageProcessor() {
  139. let p = TestCIImageProcessor(filter: .tint(Color.yellow.withAlphaComponent(0.2)))
  140. checkProcessor(p, with: "tint-yellow-02")
  141. }
  142. func testCroppingImageProcessor() {
  143. let p = CroppingImageProcessor(size: CGSize(width: 50, height: 50), anchor: CGPoint(x: 0.5, y: 0.5))
  144. XCTAssertEqual(p.identifier, "com.onevcat.Kingfisher.CroppingImageProcessor((50.0, 50.0)_(0.5, 0.5))")
  145. checkProcessor(p, with: "cropping-50-50-anchor-center")
  146. }
  147. #if os(iOS) || os(tvOS)
  148. func testImageProcessorRespectOptionScale() {
  149. let image = testImage
  150. XCTAssertEqual(image.scale, 1.0)
  151. let size = CGSize(width: 2, height: 2)
  152. let processors: [ImageProcessor] = [
  153. DefaultImageProcessor(),
  154. RoundCornerImageProcessor(cornerRadius: 1.0, targetSize: size),
  155. ResizingImageProcessor(referenceSize: size),
  156. BlurImageProcessor(blurRadius: 1.0),
  157. OverlayImageProcessor(overlay: .red),
  158. TintImageProcessor(tint: .red),
  159. ColorControlsProcessor(brightness: 0, contrast: 0, saturation: 0, inputEV: 0),
  160. BlackWhiteProcessor(),
  161. CroppingImageProcessor(size: size)
  162. ]
  163. let images = processors.map { $0.process(item: .image(image), options: [.scaleFactor(2.0)]) }
  164. images.forEach {
  165. XCTAssertEqual($0!.scale, 2.0)
  166. }
  167. }
  168. #endif
  169. }
  170. struct TestCIImageProcessor: CIImageProcessor {
  171. let identifier = "com.onevcat.kingfishertest.tint"
  172. let filter: Filter
  173. }
  174. extension ImageProcessorTests {
  175. func checkProcessor(_ p: ImageProcessor, with suffix: String, noAlpha: Bool = false) {
  176. let specifiedSuffix = getSuffix(with: suffix)
  177. let filteredImageNames = noAlpha ? nonPNGIamgeNames : imageNames
  178. let targetImages = filteredImageNames
  179. .map { $0.replacingOccurrences(of: ".", with: "-\(specifiedSuffix).") }
  180. .flatMap { name -> Image? in
  181. if #available(iOS 11, tvOS 11.0, macOS 10.13, *) {
  182. // Look for the version specified target first. Then roll back to base.
  183. return Image(fileName: name.replacingOccurrences(of: ".", with: "-iOS11.")) ??
  184. Image(fileName: name.replacingOccurrences(of: ".", with: "-macOS1013.")) ??
  185. Image(fileName: name)
  186. }
  187. return Image(fileName: name)
  188. }
  189. let resultImages = imageData(noAlpha: noAlpha).flatMap { p.process(item: .data($0), options: []) }
  190. checkImagesEqual(targetImages: targetImages, resultImages: resultImages, for: specifiedSuffix)
  191. }
  192. func checkImagesEqual(targetImages: [Image], resultImages: [Image], for suffix: String) {
  193. XCTAssertEqual(targetImages.count, resultImages.count)
  194. for (i, (resultImage, targetImage)) in zip(resultImages, targetImages).enumerated() {
  195. guard resultImage.renderEqual(to: targetImage) else {
  196. let originalName = imageNames[i]
  197. let excutingName = originalName.replacingOccurrences(of: ".", with: "-\(suffix).")
  198. XCTFail("Result image is not the same to target. Failed at: \(excutingName)) for \(originalName)")
  199. let t = targetImage.write("target-\(excutingName)")
  200. let r = resultImage.write("result-\(excutingName)")
  201. print("Expected: \(t)")
  202. print("But Got: \(r)")
  203. continue
  204. }
  205. }
  206. }
  207. func getSuffix(with ori: String) -> String {
  208. #if os(macOS)
  209. return "\(ori)-mac"
  210. #else
  211. return ori
  212. #endif
  213. }
  214. }
  215. extension ImageProcessorTests {
  216. //Helper Writer
  217. func _testWrite() {
  218. let p = BlurImageProcessor(blurRadius: 4) >> RoundCornerImageProcessor(cornerRadius: 60)
  219. let suffix = "blur-4-round-corner-60-mac"
  220. let resultImages = imageData().flatMap { p.process(item: .data($0), options: []) }
  221. for i in 0..<resultImages.count {
  222. resultImages[i].write(imageNames[i].replacingOccurrences(of: ".", with: "-\(suffix)."))
  223. }
  224. }
  225. }