EFQRCode+GIF.swift 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. //
  2. // EFQRCode+GIF.swift
  3. // EFQRCode
  4. //
  5. // Created by EyreFree on 2017/10/23.
  6. //
  7. // Copyright (c) 2017 EyreFree <eyrefree@eyrefree.org>
  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. import ImageIO
  29. #if os(macOS)
  30. import CoreServices
  31. #else
  32. import MobileCoreServices
  33. #endif
  34. public extension EFQRCode {
  35. private static let framesPerSecond = 24
  36. static var tempResultPath: URL?
  37. private static func batchWatermark(frames: inout [CGImage], generator: EFQRCodeGenerator, start: Int, end: Int) {
  38. for index in start ... end {
  39. generator.setWatermark(watermark: frames[index])
  40. if let frameWithCode = generator.generate() {
  41. frames[index] = frameWithCode
  42. }
  43. }
  44. }
  45. static func generateWithGIF(data: Data, generator: EFQRCodeGenerator, pathToSave: URL? = nil, delay: Double? = nil, loopCount: Int? = nil, useMultipleThread:Bool = false) -> Data? {
  46. if let source = CGImageSourceCreateWithData(data as CFData, nil) {
  47. var frames = source.toCGImages()
  48. var fileProperties = CGImageSourceCopyProperties(source, nil)
  49. var framePropertiesArray = frames.indices.compactMap { index in
  50. CGImageSourceCopyPropertiesAtIndex(source, index, nil)
  51. }
  52. if let delay = delay {
  53. for (index, value) in framePropertiesArray.enumerated() {
  54. if var tempDict = value as? [String: Any] {
  55. if var gifDict = tempDict[kCGImagePropertyGIFDictionary as String] as? [String: Any] {
  56. gifDict.updateValue(delay, forKey: kCGImagePropertyGIFDelayTime as String)
  57. tempDict.updateValue(gifDict, forKey: kCGImagePropertyGIFDictionary as String)
  58. }
  59. framePropertiesArray[index] = tempDict as CFDictionary
  60. }
  61. }
  62. }
  63. if let loopCount = loopCount,
  64. var tempDict = fileProperties as? [String: Any] {
  65. if var gifDict = tempDict[kCGImagePropertyGIFDictionary as String] as? [String: Any] {
  66. gifDict.updateValue(loopCount, forKey: kCGImagePropertyGIFLoopCount as String)
  67. tempDict.updateValue(gifDict, forKey: kCGImagePropertyGIFDictionary as String)
  68. }
  69. fileProperties = tempDict as CFDictionary
  70. }
  71. if useMultipleThread {
  72. let group = DispatchGroup()
  73. let threshold = frames.count / framesPerSecond
  74. var i: Int = 0
  75. while i < threshold {
  76. let local = i
  77. group.enter()
  78. DispatchQueue.global(qos: .default).async {
  79. batchWatermark(frames: &frames, generator: generator, start: local * framesPerSecond, end: (local + 1) * framesPerSecond - 1)
  80. group.leave()
  81. }
  82. i += 1
  83. }
  84. group.enter()
  85. DispatchQueue.global(qos: .default).async {
  86. batchWatermark(frames: &frames, generator: generator, start: i * 20, end: frames.count - 1)
  87. group.leave()
  88. }
  89. group.wait()
  90. } else {
  91. // Clear watermark
  92. for (index, frame) in frames.enumerated() {
  93. generator.setWatermark(watermark: frame)
  94. if let frameWithCode = generator.generate() {
  95. frames[index] = frameWithCode
  96. }
  97. }
  98. }
  99. if let fileProperties = fileProperties, framePropertiesArray.count == frames.count,
  100. let url = frames.saveToGIFFile(framePropertiesArray: framePropertiesArray,
  101. fileProperties: fileProperties, url: pathToSave) {
  102. return try? Data(contentsOf: url)
  103. }
  104. }
  105. return nil
  106. }
  107. }
  108. public extension CGImageSource {
  109. // GIF
  110. func toCGImages() -> [CGImage] {
  111. let gifCount = CGImageSourceGetCount(self)
  112. let frames: [CGImage] = ( 0 ..< gifCount ).compactMap { index in
  113. CGImageSourceCreateImageAtIndex(self, index, nil)
  114. }
  115. return frames
  116. }
  117. }
  118. extension Array where Element: CGImage {
  119. public func saveToGIFFile(framePropertiesArray: [CFDictionary], fileProperties: CFDictionary, url: URL? = nil) -> URL? {
  120. var fileURL = url
  121. if nil == fileURL {
  122. let documentsDirectoryURL: URL? = try? FileManager.default.url(
  123. for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true
  124. )
  125. // The URL to write to. If the URL already exists, the data at this location is overwritten.
  126. fileURL = documentsDirectoryURL?.appendingPathComponent("EFQRCode_temp.gif")
  127. }
  128. EFQRCode.tempResultPath = fileURL
  129. guard let url = fileURL as CFURL?,
  130. let destination = CGImageDestinationCreateWithURL(url, kUTTypeGIF, count, nil)
  131. else {
  132. return nil // Can't create path
  133. }
  134. CGImageDestinationSetProperties(destination, fileProperties)
  135. for (index, image) in enumerated() {
  136. CGImageDestinationAddImage(destination, image, framePropertiesArray[index])
  137. }
  138. guard CGImageDestinationFinalize(destination) else {
  139. // Failed to finalize the image destination
  140. return nil
  141. }
  142. return fileURL
  143. }
  144. }