UIImage+AlamofireImage.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. // UIImage+AlamofireImage.swift
  2. //
  3. // Copyright (c) 2015-2016 Alamofire Software Foundation (http://alamofire.org/)
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. import CoreGraphics
  23. import Foundation
  24. import UIKit
  25. #if os(iOS) || os(tvOS)
  26. import CoreImage
  27. #endif
  28. // MARK: Initialization
  29. private let lock = NSLock()
  30. extension UIImage {
  31. /**
  32. Initializes and returns the image object with the specified data in a thread-safe manner.
  33. It has been reported that there are thread-safety issues when initializing large amounts of images
  34. simultaneously. In the event of these issues occurring, this method can be used in place of
  35. the `init?(data:)` method.
  36. - parameter data: The data object containing the image data.
  37. - returns: An initialized `UIImage` object, or `nil` if the method failed.
  38. */
  39. public static func af_threadSafeImageWithData(data: NSData) -> UIImage? {
  40. lock.lock()
  41. let image = UIImage(data: data)
  42. lock.unlock()
  43. return image
  44. }
  45. /**
  46. Initializes and returns the image object with the specified data and scale in a thread-safe manner.
  47. It has been reported that there are thread-safety issues when initializing large amounts of images
  48. simultaneously. In the event of these issues occurring, this method can be used in place of
  49. the `init?(data:scale:)` method.
  50. - parameter data: The data object containing the image data.
  51. - parameter scale: The scale factor to assume when interpreting the image data. Applying a scale factor of 1.0
  52. results in an image whose size matches the pixel-based dimensions of the image. Applying a
  53. different scale factor changes the size of the image as reported by the size property.
  54. - returns: An initialized `UIImage` object, or `nil` if the method failed.
  55. */
  56. public static func af_threadSafeImageWithData(data: NSData, scale: CGFloat) -> UIImage? {
  57. lock.lock()
  58. let image = UIImage(data: data, scale: scale)
  59. lock.unlock()
  60. return image
  61. }
  62. }
  63. // MARK: - Inflation
  64. extension UIImage {
  65. private struct AssociatedKeys {
  66. static var InflatedKey = "af_UIImage.Inflated"
  67. }
  68. /// Returns whether the image is inflated.
  69. public var af_inflated: Bool {
  70. get {
  71. if let inflated = objc_getAssociatedObject(self, &AssociatedKeys.InflatedKey) as? Bool {
  72. return inflated
  73. } else {
  74. return false
  75. }
  76. }
  77. set(inflated) {
  78. objc_setAssociatedObject(self, &AssociatedKeys.InflatedKey, inflated, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  79. }
  80. }
  81. /**
  82. Inflates the underlying compressed image data to be backed by an uncompressed bitmap representation.
  83. Inflating compressed image formats (such as PNG or JPEG) can significantly improve drawing performance as it
  84. allows a bitmap representation to be constructed in the background rather than on the main thread.
  85. */
  86. public func af_inflate() {
  87. guard !af_inflated else { return }
  88. af_inflated = true
  89. CGDataProviderCopyData(CGImageGetDataProvider(CGImage))
  90. }
  91. }
  92. // MARK: - Alpha
  93. extension UIImage {
  94. /// Returns whether the image contains an alpha component.
  95. public var af_containsAlphaComponent: Bool {
  96. let alphaInfo = CGImageGetAlphaInfo(CGImage)
  97. return (
  98. alphaInfo == .First ||
  99. alphaInfo == .Last ||
  100. alphaInfo == .PremultipliedFirst ||
  101. alphaInfo == .PremultipliedLast
  102. )
  103. }
  104. /// Returns whether the image is opaque.
  105. public var af_isOpaque: Bool { return !af_containsAlphaComponent }
  106. }
  107. // MARK: - Scaling
  108. extension UIImage {
  109. /**
  110. Returns a new version of the image scaled to the specified size.
  111. - parameter size: The size to use when scaling the new image.
  112. - returns: A new image object.
  113. */
  114. public func af_imageScaledToSize(size: CGSize) -> UIImage {
  115. UIGraphicsBeginImageContextWithOptions(size, af_isOpaque, 0.0)
  116. drawInRect(CGRect(origin: CGPointZero, size: size))
  117. let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
  118. UIGraphicsEndImageContext()
  119. return scaledImage
  120. }
  121. /**
  122. Returns a new version of the image scaled from the center while maintaining the aspect ratio to fit within
  123. a specified size.
  124. The resulting image contains an alpha component used to pad the width or height with the necessary transparent
  125. pixels to fit the specified size. In high performance critical situations, this may not be the optimal approach.
  126. To maintain an opaque image, you could compute the `scaledSize` manually, then use the `af_imageScaledToSize`
  127. method in conjunction with a `.Center` content mode to achieve the same visual result.
  128. - parameter size: The size to use when scaling the new image.
  129. - returns: A new image object.
  130. */
  131. public func af_imageAspectScaledToFitSize(size: CGSize) -> UIImage {
  132. let imageAspectRatio = self.size.width / self.size.height
  133. let canvasAspectRatio = size.width / size.height
  134. var resizeFactor: CGFloat
  135. if imageAspectRatio > canvasAspectRatio {
  136. resizeFactor = size.width / self.size.width
  137. } else {
  138. resizeFactor = size.height / self.size.height
  139. }
  140. let scaledSize = CGSize(width: self.size.width * resizeFactor, height: self.size.height * resizeFactor)
  141. let origin = CGPoint(x: (size.width - scaledSize.width) / 2.0, y: (size.height - scaledSize.height) / 2.0)
  142. UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
  143. drawInRect(CGRect(origin: origin, size: scaledSize))
  144. let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
  145. UIGraphicsEndImageContext()
  146. return scaledImage
  147. }
  148. /**
  149. Returns a new version of the image scaled from the center while maintaining the aspect ratio to fill a
  150. specified size. Any pixels that fall outside the specified size are clipped.
  151. - parameter size: The size to use when scaling the new image.
  152. - returns: A new image object.
  153. */
  154. public func af_imageAspectScaledToFillSize(size: CGSize) -> UIImage {
  155. let imageAspectRatio = self.size.width / self.size.height
  156. let canvasAspectRatio = size.width / size.height
  157. var resizeFactor: CGFloat
  158. if imageAspectRatio > canvasAspectRatio {
  159. resizeFactor = size.height / self.size.height
  160. } else {
  161. resizeFactor = size.width / self.size.width
  162. }
  163. let scaledSize = CGSize(width: self.size.width * resizeFactor, height: self.size.height * resizeFactor)
  164. let origin = CGPoint(x: (size.width - scaledSize.width) / 2.0, y: (size.height - scaledSize.height) / 2.0)
  165. UIGraphicsBeginImageContextWithOptions(size, af_isOpaque, 0.0)
  166. drawInRect(CGRect(origin: origin, size: scaledSize))
  167. let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
  168. UIGraphicsEndImageContext()
  169. return scaledImage
  170. }
  171. }
  172. // MARK: - Rounded Corners
  173. extension UIImage {
  174. /**
  175. Returns a new version of the image with the corners rounded to the specified radius.
  176. - parameter radius: The radius to use when rounding the new image.
  177. - parameter divideRadiusByImageScale: Whether to divide the radius by the image scale. Set to `true` when the
  178. image has the same resolution for all screen scales such as @1x, @2x and
  179. @3x (i.e. single image from web server). Set to `false` for images loaded
  180. from an asset catalog with varying resolutions for each screen scale.
  181. `false` by default.
  182. - returns: A new image object.
  183. */
  184. public func af_imageWithRoundedCornerRadius(radius: CGFloat, divideRadiusByImageScale: Bool = false) -> UIImage {
  185. UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
  186. let scaledRadius = divideRadiusByImageScale ? radius / scale : radius
  187. let clippingPath = UIBezierPath(roundedRect: CGRect(origin: CGPointZero, size: size), cornerRadius: scaledRadius)
  188. clippingPath.addClip()
  189. drawInRect(CGRect(origin: CGPointZero, size: size))
  190. let roundedImage = UIGraphicsGetImageFromCurrentImageContext()
  191. UIGraphicsEndImageContext()
  192. return roundedImage
  193. }
  194. /**
  195. Returns a new version of the image rounded into a circle.
  196. - returns: A new image object.
  197. */
  198. public func af_imageRoundedIntoCircle() -> UIImage {
  199. let radius = min(size.width, size.height) / 2.0
  200. var squareImage = self
  201. if size.width != size.height {
  202. let squareDimension = min(size.width, size.height)
  203. let squareSize = CGSize(width: squareDimension, height: squareDimension)
  204. squareImage = af_imageAspectScaledToFillSize(squareSize)
  205. }
  206. UIGraphicsBeginImageContextWithOptions(squareImage.size, false, 0.0)
  207. let clippingPath = UIBezierPath(
  208. roundedRect: CGRect(origin: CGPointZero, size: squareImage.size),
  209. cornerRadius: radius
  210. )
  211. clippingPath.addClip()
  212. squareImage.drawInRect(CGRect(origin: CGPointZero, size: squareImage.size))
  213. let roundedImage = UIGraphicsGetImageFromCurrentImageContext()
  214. UIGraphicsEndImageContext()
  215. return roundedImage
  216. }
  217. }
  218. #if os(iOS) || os(tvOS)
  219. // MARK: - Core Image Filters
  220. extension UIImage {
  221. /**
  222. Returns a new version of the image using a CoreImage filter with the specified name and parameters.
  223. - parameter filterName: The name of the CoreImage filter to use on the new image.
  224. - parameter filterParameters: The parameters to apply to the CoreImage filter.
  225. - returns: A new image object, or `nil` if the filter failed for any reason.
  226. */
  227. public func af_imageWithAppliedCoreImageFilter(
  228. filterName: String,
  229. filterParameters: [String: AnyObject]? = nil) -> UIImage?
  230. {
  231. var image: CoreImage.CIImage? = CIImage
  232. if image == nil, let CGImage = self.CGImage {
  233. image = CoreImage.CIImage(CGImage: CGImage)
  234. }
  235. guard let coreImage = image else { return nil }
  236. let context = CIContext(options: [kCIContextPriorityRequestLow: true])
  237. var parameters: [String: AnyObject] = filterParameters ?? [:]
  238. parameters[kCIInputImageKey] = coreImage
  239. guard let filter = CIFilter(name: filterName, withInputParameters: parameters) else { return nil }
  240. guard let outputImage = filter.outputImage else { return nil }
  241. let cgImageRef = context.createCGImage(outputImage, fromRect: outputImage.extent)
  242. return UIImage(CGImage: cgImageRef, scale: scale, orientation: imageOrientation)
  243. }
  244. }
  245. #endif