Image.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. //
  2. // Image.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 16/1/6.
  6. //
  7. // Copyright (c) 2016 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. #if os(macOS)
  27. import AppKit
  28. public typealias Image = NSImage
  29. private var imagesKey: Void?
  30. private var durationKey: Void?
  31. #else
  32. import UIKit
  33. import MobileCoreServices
  34. public typealias Image = UIImage
  35. private var imageSourceKey: Void?
  36. private var animatedImageDataKey: Void?
  37. #endif
  38. import ImageIO
  39. import CoreGraphics
  40. // MARK: - Image Properties
  41. extension Image {
  42. #if os(macOS)
  43. var cgImage: CGImage! {
  44. return cgImage(forProposedRect: nil, context: nil, hints: nil)
  45. }
  46. var kf_scale: CGFloat {
  47. return 1.0
  48. }
  49. fileprivate(set) var kf_images: [Image]? {
  50. get {
  51. return objc_getAssociatedObject(self, &imagesKey) as? [Image]
  52. }
  53. set {
  54. objc_setAssociatedObject(self, &imagesKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  55. }
  56. }
  57. fileprivate(set) var kf_duration: TimeInterval {
  58. get {
  59. return objc_getAssociatedObject(self, &durationKey) as? TimeInterval ?? 0.0
  60. }
  61. set {
  62. objc_setAssociatedObject(self, &durationKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  63. }
  64. }
  65. var kf_size: CGSize {
  66. return representations.reduce(CGSize.zero, { size, rep in
  67. return CGSize(width: max(size.width, CGFloat(rep.pixelsWide)), height: max(size.height, CGFloat(rep.pixelsHigh)))
  68. })
  69. }
  70. #else
  71. var kf_scale: CGFloat {
  72. return scale
  73. }
  74. var kf_images: [Image]? {
  75. return images
  76. }
  77. var kf_duration: TimeInterval {
  78. return duration
  79. }
  80. fileprivate(set) var kf_imageSource: ImageSource? {
  81. get {
  82. return objc_getAssociatedObject(self, &imageSourceKey) as? ImageSource
  83. }
  84. set {
  85. objc_setAssociatedObject(self, &imageSourceKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  86. }
  87. }
  88. fileprivate(set) var kf_animatedImageData: Data? {
  89. get {
  90. return objc_getAssociatedObject(self, &animatedImageDataKey) as? Data
  91. }
  92. set {
  93. objc_setAssociatedObject(self, &animatedImageDataKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  94. }
  95. }
  96. var kf_size: CGSize {
  97. return size
  98. }
  99. #endif
  100. }
  101. // MARK: - Image Conversion
  102. extension Image {
  103. #if os(macOS)
  104. static func kf_image(cgImage: CGImage, scale: CGFloat, refImage: Image?) -> Image {
  105. return Image(cgImage: cgImage, size: CGSize.zero)
  106. }
  107. /**
  108. Normalize the image. This method does nothing in OS X.
  109. - returns: The image itself.
  110. */
  111. public func kf_normalized() -> Image {
  112. return self
  113. }
  114. static func kf_animatedImage(images: [Image], duration: TimeInterval) -> Image? {
  115. return nil
  116. }
  117. #else
  118. static func kf_image(cgImage: CGImage, scale: CGFloat, refImage: Image?) -> Image {
  119. if let refImage = refImage {
  120. return Image(cgImage: cgImage, scale: scale, orientation: refImage.imageOrientation)
  121. } else {
  122. return Image(cgImage: cgImage, scale: scale, orientation: .up)
  123. }
  124. }
  125. /**
  126. Normalize the image. This method will try to redraw an image with orientation and scale considered.
  127. - returns: The normalized image with orientation set to up and correct scale.
  128. */
  129. public func kf_normalized() -> Image {
  130. // prevent animated image (GIF) lose it's images
  131. if images != nil {
  132. return self
  133. }
  134. if imageOrientation == .up {
  135. return self
  136. }
  137. UIGraphicsBeginImageContextWithOptions(size, false, scale)
  138. draw(in: CGRect(origin: CGPoint.zero, size: size))
  139. let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
  140. UIGraphicsEndImageContext()
  141. return normalizedImage!
  142. }
  143. static func kf_animated(with images: [Image], forDuration duration: TimeInterval) -> Image? {
  144. return Image.animatedImage(with: images, duration: duration)
  145. }
  146. #endif
  147. }
  148. extension Image {
  149. // MARK: - PNG
  150. func pngRepresentation() -> Data? {
  151. #if os(macOS)
  152. if let cgimage = cgImage {
  153. let rep = NSBitmapImageRep(cgImage: cgimage)
  154. return rep.representation(using: .PNG, properties: [:])
  155. }
  156. return nil
  157. #else
  158. return UIImagePNGRepresentation(self)
  159. #endif
  160. }
  161. // MARK: - JPEG
  162. func jpegRepresentation(compressionQuality: CGFloat) -> Data? {
  163. #if os(macOS)
  164. let rep = NSBitmapImageRep(cgImage: cgImage)
  165. return rep.representation(using:.JPEG, properties: [NSImageCompressionFactor: compressionQuality])
  166. #else
  167. return UIImageJPEGRepresentation(self, compressionQuality)
  168. #endif
  169. }
  170. func gifRepresentation() -> Data? {
  171. #if os(macOS)
  172. return gifRepresentation(duration: 0.0, repeatCount: 0)
  173. #else
  174. return kf_animatedImageData
  175. #endif
  176. }
  177. func gifRepresentation(duration: TimeInterval, repeatCount: Int) -> Data? {
  178. guard let images = kf_images else {
  179. return nil
  180. }
  181. let frameCount = images.count
  182. let gifDuration = duration <= 0.0 ? kf_duration / Double(frameCount) : duration / Double(frameCount)
  183. let frameProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: gifDuration]]
  184. let imageProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: repeatCount]]
  185. let data = NSMutableData()
  186. guard let destination = CGImageDestinationCreateWithData(data, kUTTypeGIF, frameCount, nil) else {
  187. return nil
  188. }
  189. CGImageDestinationSetProperties(destination, imageProperties as CFDictionary)
  190. for image in images {
  191. CGImageDestinationAddImage(destination, image.cgImage!, frameProperties as CFDictionary)
  192. }
  193. return CGImageDestinationFinalize(destination) ? data.copy() as? Data : nil
  194. }
  195. }
  196. extension Image {
  197. static func kf_animated(with data: Data, preloadAll: Bool) -> Image? {
  198. return kf_animated(with: data, scale: 1.0, duration: 0.0, preloadAll: preloadAll)
  199. }
  200. static func kf_animated(with data: Data, scale: CGFloat, duration: TimeInterval, preloadAll: Bool) -> Image? {
  201. func decode(from imageSource: CGImageSource, for options: NSDictionary) -> ([Image], TimeInterval)? {
  202. //Calculates frame duration for a gif frame out of the kCGImagePropertyGIFDictionary dictionary
  203. func frameDuration(from gifInfo: NSDictionary) -> Double {
  204. let gifDefaultFrameDuration = 0.100
  205. let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber
  206. let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber
  207. let duration = unclampedDelayTime ?? delayTime
  208. guard let frameDuration = duration else { return gifDefaultFrameDuration }
  209. return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : gifDefaultFrameDuration
  210. }
  211. let frameCount = CGImageSourceGetCount(imageSource)
  212. var images = [Image]()
  213. var gifDuration = 0.0
  214. for i in 0 ..< frameCount {
  215. guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, options) else {
  216. return nil
  217. }
  218. if frameCount == 1 {
  219. // Single frame
  220. gifDuration = Double.infinity
  221. } else {
  222. // Animated GIF
  223. guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil),
  224. let gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary else
  225. {
  226. return nil
  227. }
  228. gifDuration += frameDuration(from: gifInfo)
  229. }
  230. images.append(Image.kf_image(cgImage: imageRef, scale: scale, refImage: nil))
  231. }
  232. return (images, gifDuration)
  233. }
  234. // Start of kf_animatedImageWithGIFData
  235. let options: NSDictionary = [kCGImageSourceShouldCache as String: true, kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF]
  236. guard let imageSource = CGImageSourceCreateWithData(data as CFData, options) else {
  237. return nil
  238. }
  239. #if os(macOS)
  240. guard let (images, gifDuration) = decode(from: imageSource, for: options) else {
  241. return nil
  242. }
  243. let image = Image(data: data)
  244. image?.kf_images = images
  245. image?.kf_duration = gifDuration
  246. return image
  247. #else
  248. if preloadAll {
  249. guard let (images, gifDuration) = decode(from: imageSource, for: options) else {
  250. return nil
  251. }
  252. let image = Image.kf_animated(with: images, forDuration: duration <= 0.0 ? gifDuration : duration)
  253. image?.kf_animatedImageData = data
  254. return image
  255. } else {
  256. let image = Image(data: data)
  257. image?.kf_animatedImageData = data
  258. image?.kf_imageSource = ImageSource(ref: imageSource)
  259. return image
  260. }
  261. #endif
  262. }
  263. }
  264. // MARK: - Create images from data
  265. extension Image {
  266. static func kf_image(data: Data, scale: CGFloat, preloadAllGIFData: Bool) -> Image? {
  267. var image: Image?
  268. #if os(macOS)
  269. switch data.kf_imageFormat {
  270. case .JPEG: image = Image(data: data)
  271. case .PNG: image = Image(data: data)
  272. case .GIF: image = Image.kf_animated(with: data, scale: scale, duration: 0.0, preloadAll: preloadAllGIFData)
  273. case .unknown: image = Image(data: data)
  274. }
  275. #else
  276. switch data.kf_imageFormat {
  277. case .JPEG: image = Image(data: data, scale: scale)
  278. case .PNG: image = Image(data: data, scale: scale)
  279. case .GIF: image = Image.kf_animated(with: data, scale: scale, duration: 0.0, preloadAll: preloadAllGIFData)
  280. case .unknown: image = Image(data: data, scale: scale)
  281. }
  282. #endif
  283. return image
  284. }
  285. }
  286. extension Image {
  287. func kf_image(withRoundRadius radius: CGFloat, fit size: CGSize, scale: CGFloat) -> Image? {
  288. let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: size)
  289. #if os(macOS)
  290. let output = NSImage(size: rect.size)
  291. output.lockFocus()
  292. NSGraphicsContext.current()?.imageInterpolation = .high
  293. let path = NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius)
  294. path.windingRule = .evenOddWindingRule
  295. path.addClip()
  296. draw(in: rect)
  297. output.unlockFocus()
  298. #else
  299. UIGraphicsBeginImageContextWithOptions(rect.size, false, scale)
  300. guard let context = UIGraphicsGetCurrentContext() else {
  301. return nil
  302. }
  303. let path = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: radius, height: radius)).cgPath
  304. context.addPath(path)
  305. context.clip()
  306. draw(in: rect)
  307. let output = UIGraphicsGetImageFromCurrentImageContext();
  308. UIGraphicsEndImageContext();
  309. #endif
  310. return output
  311. }
  312. }
  313. // MARK: - Decode
  314. extension Image {
  315. func kf_decoded() -> Image? {
  316. return self.kf_decoded(scale: kf_scale)
  317. }
  318. func kf_decoded(scale: CGFloat) -> Image? {
  319. // prevent animated image (GIF) lose it's images
  320. #if os(iOS)
  321. if kf_imageSource != nil {
  322. return self
  323. }
  324. #else
  325. if kf_images != nil {
  326. return self
  327. }
  328. #endif
  329. let imageRef = self.cgImage
  330. let colorSpace = CGColorSpaceCreateDeviceRGB()
  331. let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue).rawValue
  332. let context = CGContext(data: nil, width: (imageRef?.width)!, height: (imageRef?.height)!, bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo)
  333. if let context = context {
  334. let rect = CGRect(x: 0, y: 0, width: (imageRef?.width)!, height: (imageRef?.height)!)
  335. context.draw(imageRef!, in: rect)
  336. let decompressedImageRef = context.makeImage()
  337. return Image.kf_image(cgImage: decompressedImageRef!, scale: scale, refImage: self)
  338. } else {
  339. return nil
  340. }
  341. }
  342. }
  343. // MARK: - Resize
  344. extension Image {
  345. #if os(iOS) || os(tvOS)
  346. func kf_resize(to size: CGSize, for contentMode: UIViewContentMode) -> Image {
  347. switch contentMode {
  348. case .scaleAspectFit:
  349. let newSize = self.size.kf_constrained(size)
  350. return kf_resize(to: newSize)
  351. case .scaleAspectFill:
  352. let newSize = self.size.kf_filling(size)
  353. return kf_resize(to: newSize)
  354. default:
  355. return kf_resize(to: size)
  356. }
  357. }
  358. #endif
  359. func kf_resize(to size: CGSize) -> Image {
  360. guard let imageRef = cgImage else {
  361. assertionFailure("[Kingfisher] Resizing only works for CG-based image.")
  362. return self
  363. }
  364. guard kf_size.width >= size.width && kf_size.height >= size.height && size.width > 0 && size.height > 0 else {
  365. print("[Kingfisher] Invalid resizing target size: \(size). The size should be smaller than original size and larger than 0")
  366. return self
  367. }
  368. let bitsPerComponent = imageRef.bitsPerComponent
  369. let bytesPerRow = imageRef.bytesPerRow
  370. let colorSpace = imageRef.colorSpace
  371. let bitmapInfo = imageRef.bitmapInfo
  372. guard let context = CGContext(data: nil,
  373. width: Int(size.width),
  374. height: Int(size.height),
  375. bitsPerComponent: bitsPerComponent,
  376. bytesPerRow: bytesPerRow,
  377. space: colorSpace!,
  378. bitmapInfo: bitmapInfo.rawValue) else
  379. {
  380. assertionFailure("[Kingfisher] Failed to create CG context for resizing image.")
  381. return self
  382. }
  383. context.interpolationQuality = .high
  384. context.draw(imageRef, in: CGRect(origin: CGPoint.zero, size: size))
  385. #if os(macOS)
  386. let result = context.makeImage().flatMap { Image(cgImage: $0, size: size) }
  387. #else
  388. let result = context.makeImage().flatMap { Image(cgImage: $0) }
  389. #endif
  390. guard let scaledImage = result else {
  391. assertionFailure("Can not make an resized image within context.")
  392. return self
  393. }
  394. return scaledImage
  395. }
  396. }
  397. extension CGSize {
  398. func kf_constrained(_ size: CGSize) -> CGSize {
  399. let aspectWidth = round(kf_aspectRatio * size.height)
  400. let aspectHeight = round(size.width / kf_aspectRatio)
  401. return aspectWidth > size.width ? CGSize(width: size.width, height: aspectHeight) : CGSize(width: aspectWidth, height: size.height)
  402. }
  403. func kf_filling(_ size: CGSize) -> CGSize {
  404. let aspectWidth = round(kf_aspectRatio * size.height)
  405. let aspectHeight = round(size.width / kf_aspectRatio)
  406. return aspectWidth < size.width ? CGSize(width: size.width, height: aspectHeight) : CGSize(width: aspectWidth, height: size.height)
  407. }
  408. private var kf_aspectRatio: CGFloat {
  409. return height == 0.0 ? 1.0 : width / height
  410. }
  411. }
  412. /// Reference the source image reference
  413. class ImageSource {
  414. var imageRef: CGImageSource?
  415. init(ref: CGImageSource) {
  416. self.imageRef = ref
  417. }
  418. }
  419. // MARK: - Image format
  420. private struct ImageHeaderData {
  421. static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
  422. static var JPEG_SOI: [UInt8] = [0xFF, 0xD8]
  423. static var JPEG_IF: [UInt8] = [0xFF]
  424. static var GIF: [UInt8] = [0x47, 0x49, 0x46]
  425. }
  426. enum ImageFormat {
  427. case unknown, PNG, JPEG, GIF
  428. }
  429. extension Data {
  430. var kf_imageFormat: ImageFormat {
  431. var buffer = [UInt8](repeating: 0, count: 8)
  432. (self as NSData).getBytes(&buffer, length: 8)
  433. if buffer == ImageHeaderData.PNG {
  434. return .PNG
  435. } else if buffer[0] == ImageHeaderData.JPEG_SOI[0] &&
  436. buffer[1] == ImageHeaderData.JPEG_SOI[1] &&
  437. buffer[2] == ImageHeaderData.JPEG_IF[0]
  438. {
  439. return .JPEG
  440. } else if buffer[0] == ImageHeaderData.GIF[0] &&
  441. buffer[1] == ImageHeaderData.GIF[1] &&
  442. buffer[2] == ImageHeaderData.GIF[2]
  443. {
  444. return .GIF
  445. }
  446. return .unknown
  447. }
  448. }