EFQRCodeGenerator.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. //
  2. // EFQRCodeGenerator.swift
  3. // EFQRCode
  4. //
  5. // Created by EyreFree on 17/1/24.
  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. #if os(watchOS)
  27. import CoreGraphics
  28. import swift_qrcodejs
  29. #else
  30. import CoreImage
  31. #endif
  32. // EFQRCode+Create
  33. @objcMembers
  34. public class EFQRCodeGenerator: NSObject {
  35. // MARK: - Parameters
  36. // Content of QR Code
  37. private var content: String? {
  38. didSet {
  39. imageQRCode = nil
  40. imageCodes = nil
  41. }
  42. }
  43. public func setContent(content: String) {
  44. self.content = content
  45. }
  46. // Mode of QR Code
  47. private var mode: EFQRCodeMode = .none {
  48. didSet {
  49. imageQRCode = nil
  50. }
  51. }
  52. public func setMode(mode: EFQRCodeMode) {
  53. self.mode = mode
  54. }
  55. // Error-tolerant rate
  56. // L 7%
  57. // M 15%
  58. // Q 25%
  59. // H 30%(Default)
  60. private var inputCorrectionLevel: EFInputCorrectionLevel = .h {
  61. didSet {
  62. imageQRCode = nil
  63. imageCodes = nil
  64. }
  65. }
  66. public func setInputCorrectionLevel(inputCorrectionLevel: EFInputCorrectionLevel) {
  67. self.inputCorrectionLevel = inputCorrectionLevel
  68. }
  69. // Size of QR Code
  70. private var size: EFIntSize = EFIntSize(width: 256, height: 256) {
  71. didSet {
  72. imageQRCode = nil
  73. }
  74. }
  75. public func setSize(size: EFIntSize) {
  76. self.size = size
  77. }
  78. // Magnification of QRCode compare with the minimum size,
  79. // (Parameter size will be ignored if magnification is not nil).
  80. private var magnification: EFIntSize? {
  81. didSet {
  82. imageQRCode = nil
  83. }
  84. }
  85. public func setMagnification(magnification: EFIntSize?) {
  86. self.magnification = magnification
  87. }
  88. // backgroundColor
  89. private var backgroundColor: CGColor = CGColor.EFWhite() {
  90. didSet {
  91. imageQRCode = nil
  92. }
  93. }
  94. // foregroundColor
  95. private var foregroundColor: CGColor = CGColor.EFBlack() {
  96. didSet {
  97. imageQRCode = nil
  98. }
  99. }
  100. #if os(iOS) || os(tvOS) || os(macOS)
  101. @nonobjc public func setColors(backgroundColor: CIColor, foregroundColor: CIColor) {
  102. self.backgroundColor = backgroundColor.toCGColor() ?? .EFWhite()
  103. self.foregroundColor = foregroundColor.toCGColor() ?? .EFBlack()
  104. }
  105. #endif
  106. public func setColors(backgroundColor: CGColor, foregroundColor: CGColor) {
  107. self.backgroundColor = backgroundColor
  108. self.foregroundColor = foregroundColor
  109. }
  110. // Icon in the middle of QR Code
  111. private var icon: CGImage? = nil {
  112. didSet {
  113. imageQRCode = nil
  114. }
  115. }
  116. // Size of icon
  117. private var iconSize: EFIntSize? = nil {
  118. didSet {
  119. imageQRCode = nil
  120. }
  121. }
  122. public func setIcon(icon: CGImage?, size: EFIntSize?) {
  123. self.icon = icon
  124. self.iconSize = size
  125. }
  126. // Watermark
  127. private var watermark: CGImage? = nil {
  128. didSet {
  129. imageQRCode = nil
  130. }
  131. }
  132. // Mode of watermark
  133. private var watermarkMode: EFWatermarkMode = .scaleAspectFill {
  134. didSet {
  135. imageQRCode = nil
  136. }
  137. }
  138. public func setWatermark(watermark: CGImage?, mode: EFWatermarkMode? = nil) {
  139. self.watermark = watermark
  140. if let mode = mode {
  141. self.watermarkMode = mode
  142. }
  143. }
  144. // Offset of foreground point
  145. private var foregroundPointOffset: CGFloat = 0 {
  146. didSet {
  147. imageQRCode = nil
  148. }
  149. }
  150. public func setForegroundPointOffset(foregroundPointOffset: CGFloat) {
  151. self.foregroundPointOffset = foregroundPointOffset
  152. }
  153. // Alpha 0 area of watermark will transparent
  154. private var allowTransparent: Bool = true {
  155. didSet {
  156. imageQRCode = nil
  157. }
  158. }
  159. public func setAllowTransparent(allowTransparent: Bool) {
  160. self.allowTransparent = allowTransparent
  161. }
  162. // Shape of foreground point
  163. private var pointShape: EFPointShape = .square {
  164. didSet {
  165. imageQRCode = nil
  166. }
  167. }
  168. public func setPointShape(pointShape: EFPointShape) {
  169. self.pointShape = pointShape
  170. }
  171. // Threshold for binarization (Only for mode binarization).
  172. private var binarizationThreshold: CGFloat = 0.5 {
  173. didSet {
  174. imageQRCode = nil
  175. }
  176. }
  177. public func setBinarizationThreshold(binarizationThreshold: CGFloat) {
  178. self.binarizationThreshold = binarizationThreshold
  179. }
  180. // Cache
  181. private var imageCodes: [[Bool]]?
  182. private var imageQRCode: CGImage?
  183. private var minSuitableSize: EFIntSize!
  184. // MARK: - Init
  185. public init(
  186. content: String,
  187. size: EFIntSize = EFIntSize(width: 256, height: 256)
  188. ) {
  189. self.content = content
  190. self.size = size
  191. }
  192. /// Final QRCode image
  193. public func generate() -> CGImage? {
  194. if nil == imageQRCode {
  195. imageQRCode = createImageQRCode()
  196. }
  197. return imageQRCode
  198. }
  199. // MARK: - Draw
  200. private func createImageQRCode() -> CGImage? {
  201. var finalSize = self.size
  202. let finalBackgroundColor = getBackgroundColor()
  203. let finalForegroundColor = getForegroundColor()
  204. let finalIcon = self.icon
  205. let finalIconSize = self.iconSize
  206. let finalWatermark = self.watermark
  207. let finalWatermarkMode = self.watermarkMode
  208. // Get QRCodes from image
  209. guard let codes = generateCodes() else {
  210. return nil
  211. }
  212. // If magnification is not nil, reset finalSize
  213. if let tryMagnification = magnification {
  214. finalSize = EFIntSize(
  215. width: tryMagnification.width * codes.count, height: tryMagnification.height * codes.count
  216. )
  217. }
  218. var result: CGImage?
  219. if let context = createContext(size: finalSize) {
  220. // Cache size
  221. minSuitableSize = EFIntSize(
  222. width: minSuitableSizeGreaterThanOrEqualTo(size: finalSize.widthCGFloat()) ?? finalSize.width,
  223. height: minSuitableSizeGreaterThanOrEqualTo(size: finalSize.heightCGFloat()) ?? finalSize.height
  224. )
  225. // Watermark
  226. if let tryWatermark = finalWatermark {
  227. // Draw background with watermark
  228. drawWatermarkImage(
  229. context: context,
  230. image: tryWatermark,
  231. colorBack: finalBackgroundColor,
  232. mode: finalWatermarkMode,
  233. size: finalSize.toCGSize()
  234. )
  235. // Draw QR Code
  236. if let tryFrontImage = createQRCodeImageTransparent(
  237. codes: codes,
  238. colorBack: finalBackgroundColor,
  239. colorFront: finalForegroundColor,
  240. size: minSuitableSize
  241. ) {
  242. context.draw(tryFrontImage, in: CGRect(origin: .zero, size: finalSize.toCGSize()))
  243. }
  244. } else {
  245. // Draw background without watermark
  246. let colorCGBack = finalBackgroundColor
  247. context.setFillColor(colorCGBack)
  248. context.fill(CGRect(origin: .zero, size: finalSize.toCGSize()))
  249. // Draw QR Code
  250. if let tryImage = createQRCodeImage(
  251. codes: codes,
  252. colorBack: finalBackgroundColor,
  253. colorFront: finalForegroundColor,
  254. size: minSuitableSize
  255. ) {
  256. context.draw(tryImage, in: CGRect(origin: .zero, size: finalSize.toCGSize()))
  257. }
  258. }
  259. // Add icon
  260. if let tryIcon = finalIcon {
  261. var finalIconSizeWidth = finalSize.widthCGFloat() * 0.2
  262. var finalIconSizeHeight = finalSize.heightCGFloat() * 0.2
  263. if let tryFinalIconSize = finalIconSize {
  264. finalIconSizeWidth = tryFinalIconSize.widthCGFloat()
  265. finalIconSizeHeight = tryFinalIconSize.heightCGFloat()
  266. }
  267. let maxLength = [CGFloat(0.2), 0.3, 0.4, 0.5][inputCorrectionLevel.rawValue] * finalSize.widthCGFloat()
  268. if finalIconSizeWidth > maxLength {
  269. finalIconSizeWidth = maxLength
  270. print("Warning: icon width too big, it has been changed.")
  271. }
  272. if finalIconSizeHeight > maxLength {
  273. finalIconSizeHeight = maxLength
  274. print("Warning: icon height too big, it has been changed.")
  275. }
  276. let iconSize = EFIntSize(width: Int(finalIconSizeWidth), height: Int(finalIconSizeHeight))
  277. drawIcon(
  278. context: context,
  279. icon: tryIcon,
  280. size: iconSize
  281. )
  282. }
  283. result = context.makeImage()
  284. }
  285. // Mode apply
  286. switch mode {
  287. case .grayscale:
  288. if let tryModeImage = result?.grayscale() {
  289. result = tryModeImage
  290. }
  291. case .binarization:
  292. if let tryModeImage = result?.binarization(
  293. value: binarizationThreshold,
  294. foregroundColor: foregroundColor,
  295. backgroundColor: backgroundColor
  296. ) {
  297. result = tryModeImage
  298. }
  299. case .none:
  300. break
  301. }
  302. return result
  303. }
  304. private func getForegroundColor() -> CGColor {
  305. if mode == .binarization {
  306. return .EFBlack()
  307. }
  308. return foregroundColor
  309. }
  310. private func getBackgroundColor() -> CGColor {
  311. if mode == .binarization {
  312. return .EFWhite()
  313. }
  314. return backgroundColor
  315. }
  316. // Create Colorful QR Image
  317. #if os(iOS) || os(tvOS) || os(macOS)
  318. private func createQRCodeImage(
  319. codes: [[Bool]],
  320. colorBack: CIColor,
  321. colorFront: CIColor,
  322. size: EFIntSize) -> CGImage? {
  323. guard let colorCGFront = colorFront.toCGColor() else {
  324. return nil
  325. }
  326. return createQRCodeImage(codes: codes, colorFront: colorCGFront, size: size)
  327. }
  328. #endif
  329. private func createQRCodeImage(
  330. codes: [[Bool]],
  331. colorBack colorCGBack: CGColor? = nil,
  332. colorFront colorCGFront: CGColor,
  333. size: EFIntSize) -> CGImage? {
  334. let codeSize = codes.count
  335. let scaleX = size.widthCGFloat() / CGFloat(codeSize)
  336. let scaleY = size.heightCGFloat() / CGFloat(codeSize)
  337. if scaleX < 1.0 || scaleY < 1.0 {
  338. print("Warning: Size too small.")
  339. }
  340. var points = [CGPoint]()
  341. if let locations = getAlignmentPatternLocations(version: getVersion(size: codeSize - 2)) {
  342. for indexX in locations {
  343. for indexY in locations {
  344. let finalX = indexX + 1
  345. let finalY = indexY + 1
  346. if !((finalX == 7 && finalY == 7)
  347. || (finalX == 7 && finalY == (codeSize - 8))
  348. || (finalX == (codeSize - 8) && finalY == 7)) {
  349. points.append(CGPoint(x: finalX, y: finalY))
  350. }
  351. }
  352. }
  353. }
  354. var result: CGImage?
  355. if let context = createContext(size: size) {
  356. // Point
  357. context.setFillColor(colorCGFront)
  358. for indexY in 0 ..< codeSize {
  359. for indexX in 0 ..< codeSize where codes[indexX][indexY] {
  360. // CTM-90
  361. let indexXCTM = indexY
  362. let indexYCTM = codeSize - indexX - 1
  363. let isStaticPoint = isStatic(x: indexX, y: indexY, size: codeSize, APLPoints: points)
  364. drawPoint(
  365. context: context,
  366. rect: CGRect(
  367. x: CGFloat(indexXCTM) * scaleX + foregroundPointOffset,
  368. y: CGFloat(indexYCTM) * scaleY + foregroundPointOffset,
  369. width: scaleX - 2 * foregroundPointOffset,
  370. height: scaleY - 2 * foregroundPointOffset
  371. ),
  372. isStatic: isStaticPoint
  373. )
  374. }
  375. }
  376. result = context.makeImage()
  377. }
  378. return result
  379. }
  380. // Create Colorful QR Image
  381. #if os(iOS) || os(tvOS) || os(macOS)
  382. private func createQRCodeImageTransparent(
  383. codes: [[Bool]],
  384. colorBack: CIColor,
  385. colorFront: CIColor,
  386. size: EFIntSize) -> CGImage? {
  387. guard let colorCGBack = colorBack.toCGColor(), let colorCGFront = colorFront.toCGColor() else {
  388. return nil
  389. }
  390. return createQRCodeImageTransparent(codes: codes, colorBack: colorCGBack, colorFront: colorCGFront, size: size)
  391. }
  392. #endif
  393. private func createQRCodeImageTransparent(
  394. codes: [[Bool]],
  395. colorBack colorCGBack: CGColor,
  396. colorFront colorCGFront: CGColor,
  397. size: EFIntSize) -> CGImage? {
  398. let codeSize = codes.count
  399. let scaleX = size.widthCGFloat() / CGFloat(codeSize)
  400. let scaleY = size.heightCGFloat() / CGFloat(codeSize)
  401. if scaleX < 1.0 || scaleY < 1.0 {
  402. print("Warning: Size too small.")
  403. }
  404. let pointMinOffsetX = scaleX / 3
  405. let pointMinOffsetY = scaleY / 3
  406. let pointWidthOriX = scaleX
  407. let pointWidthOriY = scaleY
  408. let pointWidthMinX = scaleX - 2 * pointMinOffsetX
  409. let pointWidthMinY = scaleY - 2 * pointMinOffsetY
  410. // Get AlignmentPatternLocations first
  411. var points = [CGPoint]()
  412. if let locations = getAlignmentPatternLocations(version: getVersion(size: codeSize - 2)) {
  413. for indexX in locations {
  414. for indexY in locations {
  415. let finalX = indexX + 1
  416. let finalY = indexY + 1
  417. if !((finalX == 7 && finalY == 7)
  418. || (finalX == 7 && finalY == (codeSize - 8))
  419. || (finalX == (codeSize - 8) && finalY == 7)) {
  420. points.append(CGPoint(x: finalX, y: finalY))
  421. }
  422. }
  423. }
  424. }
  425. var finalImage: CGImage?
  426. if let context = createContext(size: size) {
  427. // Back point
  428. context.setFillColor(colorCGBack)
  429. for indexY in 0 ..< codeSize {
  430. for indexX in 0 ..< codeSize where !codes[indexX][indexY] {
  431. // CTM-90
  432. let indexXCTM = indexY
  433. let indexYCTM = codeSize - indexX - 1
  434. if isStatic(x: indexX, y: indexY, size: codeSize, APLPoints: points) {
  435. drawPoint(
  436. context: context,
  437. rect: CGRect(
  438. x: CGFloat(indexXCTM) * scaleX,
  439. y: CGFloat(indexYCTM) * scaleY,
  440. width: pointWidthOriX,
  441. height: pointWidthOriY
  442. ),
  443. isStatic: true
  444. )
  445. } else {
  446. drawPoint(
  447. context: context,
  448. rect: CGRect(
  449. x: CGFloat(indexXCTM) * scaleX + pointMinOffsetX,
  450. y: CGFloat(indexYCTM) * scaleY + pointMinOffsetY,
  451. width: pointWidthMinX,
  452. height: pointWidthMinY
  453. )
  454. )
  455. }
  456. }
  457. }
  458. // Front point
  459. context.setFillColor(colorCGFront)
  460. for indexY in 0 ..< codeSize {
  461. for indexX in 0 ..< codeSize where codes[indexX][indexY] {
  462. // CTM-90
  463. let indexXCTM = indexY
  464. let indexYCTM = codeSize - indexX - 1
  465. if isStatic(x: indexX, y: indexY, size: codeSize, APLPoints: points) {
  466. drawPoint(
  467. context: context,
  468. rect: CGRect(
  469. x: CGFloat(indexXCTM) * scaleX + foregroundPointOffset,
  470. y: CGFloat(indexYCTM) * scaleY + foregroundPointOffset,
  471. width: pointWidthOriX - 2 * foregroundPointOffset,
  472. height: pointWidthOriY - 2 * foregroundPointOffset
  473. ),
  474. isStatic: true
  475. )
  476. } else {
  477. drawPoint(
  478. context: context,
  479. rect: CGRect(
  480. x: CGFloat(indexXCTM) * scaleX + pointMinOffsetX,
  481. y: CGFloat(indexYCTM) * scaleY + pointMinOffsetY,
  482. width: pointWidthMinX,
  483. height: pointWidthMinY
  484. )
  485. )
  486. }
  487. }
  488. }
  489. finalImage = context.makeImage()
  490. }
  491. return finalImage
  492. }
  493. // Pre
  494. #if os(iOS) || os(tvOS) || os(macOS)
  495. private func drawWatermarkImage(
  496. context: CGContext,
  497. image: CGImage,
  498. colorBack: CIColor,
  499. mode: EFWatermarkMode,
  500. size: CGSize) {
  501. drawWatermarkImage(context: context, image: image, colorBack: colorBack.toCGColor(), mode: mode, size: size)
  502. }
  503. #endif
  504. private func drawWatermarkImage(
  505. context: CGContext,
  506. image: CGImage,
  507. colorBack: CGColor?,
  508. mode: EFWatermarkMode,
  509. size: CGSize) {
  510. // BGColor
  511. if let tryColor = colorBack {
  512. context.setFillColor(tryColor)
  513. context.fill(CGRect(origin: .zero, size: size))
  514. }
  515. if allowTransparent {
  516. guard let codes = generateCodes() else {
  517. return
  518. }
  519. if let tryCGImage = createQRCodeImage(
  520. codes: codes,
  521. colorBack: getBackgroundColor(),
  522. colorFront: getForegroundColor(),
  523. size: minSuitableSize
  524. ) {
  525. context.draw(tryCGImage, in: CGRect(origin: .zero, size: size))
  526. }
  527. }
  528. // Image
  529. var finalSize = size
  530. var finalOrigin = CGPoint.zero
  531. let imageSize = CGSize(width: image.width, height: image.height)
  532. switch mode {
  533. case .bottom:
  534. finalSize = imageSize
  535. finalOrigin = CGPoint(x: (size.width - imageSize.width) / 2.0, y: 0)
  536. case .bottomLeft:
  537. finalSize = imageSize
  538. finalOrigin = .zero
  539. case .bottomRight:
  540. finalSize = imageSize
  541. finalOrigin = CGPoint(x: size.width - imageSize.width, y: 0)
  542. case .center:
  543. finalSize = imageSize
  544. finalOrigin = CGPoint(x: (size.width - imageSize.width) / 2.0, y: (size.height - imageSize.height) / 2.0)
  545. case .left:
  546. finalSize = imageSize
  547. finalOrigin = CGPoint(x: 0, y: (size.height - imageSize.height) / 2.0)
  548. case .right:
  549. finalSize = imageSize
  550. finalOrigin = CGPoint(x: size.width - imageSize.width, y: (size.height - imageSize.height) / 2.0)
  551. case .top:
  552. finalSize = imageSize
  553. finalOrigin = CGPoint(x: (size.width - imageSize.width) / 2.0, y: size.height - imageSize.height)
  554. case .topLeft:
  555. finalSize = imageSize
  556. finalOrigin = CGPoint(x: 0, y: size.height - imageSize.height)
  557. case .topRight:
  558. finalSize = imageSize
  559. finalOrigin = CGPoint(x: size.width - imageSize.width, y: size.height - imageSize.height)
  560. case .scaleAspectFill:
  561. let scale = max(size.width / imageSize.width, size.height / imageSize.height)
  562. finalSize = CGSize(width: imageSize.width * scale, height: imageSize.height * scale)
  563. finalOrigin = CGPoint(x: (size.width - finalSize.width) / 2.0, y: (size.height - finalSize.height) / 2.0)
  564. case .scaleAspectFit:
  565. let scale = max(imageSize.width / size.width, imageSize.height / size.height)
  566. finalSize = CGSize(width: imageSize.width / scale, height: imageSize.height / scale)
  567. finalOrigin = CGPoint(x: (size.width - finalSize.width) / 2.0, y: (size.height - finalSize.height) / 2.0)
  568. case .scaleToFill:
  569. break
  570. }
  571. context.draw(image, in: CGRect(origin: finalOrigin, size: finalSize))
  572. }
  573. private func drawIcon(context: CGContext, icon: CGImage, size: EFIntSize) {
  574. context.draw(
  575. icon,
  576. in: CGRect(
  577. origin: CGPoint(
  578. x: CGFloat(context.width - size.width) / 2.0,
  579. y: CGFloat(context.height - size.height) / 2.0
  580. ),
  581. size: size.toCGSize()
  582. )
  583. )
  584. }
  585. private func fillDiamond(context: CGContext, rect: CGRect) {
  586. // shrink rect edge
  587. let drawingRect = rect.insetBy(dx: -2, dy: -2)
  588. // create path
  589. let path = CGMutablePath()
  590. // Bezier Control Point
  591. let controlPoint = CGPoint(x: drawingRect.midX , y: drawingRect.midY)
  592. // Bezier Start Point
  593. let startPoint = CGPoint(x: drawingRect.minX, y: drawingRect.midY)
  594. // the other point of diamond
  595. let otherPoints = [CGPoint(x: drawingRect.midX, y: drawingRect.maxY),
  596. CGPoint(x: drawingRect.maxX, y: drawingRect.midY),
  597. CGPoint(x: drawingRect.midX, y: drawingRect.minY)]
  598. path.move(to: startPoint)
  599. for point in otherPoints {
  600. path.addQuadCurve(to: point, control: controlPoint)
  601. }
  602. path.addQuadCurve(to: startPoint, control: controlPoint)
  603. context.addPath(path)
  604. context.fillPath()
  605. }
  606. private func drawPoint(context: CGContext, rect: CGRect, isStatic: Bool = false) {
  607. switch pointShape {
  608. case .circle:
  609. context.fillEllipse(in: rect)
  610. case .diamond:
  611. if isStatic {
  612. context.fill(rect)
  613. } else {
  614. fillDiamond(context: context, rect: rect)
  615. }
  616. case .square:
  617. context.fill(rect)
  618. }
  619. }
  620. private func createContext(size: EFIntSize) -> CGContext? {
  621. return CGContext(
  622. data: nil, width: size.width, height: size.height,
  623. bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(),
  624. bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
  625. )
  626. }
  627. #if os(iOS) || os(tvOS) || os(macOS)
  628. // MARK: - Data
  629. private func getPixels() -> [[EFUIntPixel]]? {
  630. guard let finalContent = content else {
  631. return nil
  632. }
  633. let finalInputCorrectionLevel = inputCorrectionLevel
  634. guard let tryQRImagePixels = CIImage.generateQRCode(
  635. string: finalContent, inputCorrectionLevel: finalInputCorrectionLevel
  636. )?.toCGImage()?.pixels() else {
  637. print("Warning: Content too large.")
  638. return nil
  639. }
  640. return tryQRImagePixels
  641. }
  642. #endif
  643. // Get QRCodes from pixels
  644. private func getCodes(pixels: [[EFUIntPixel]]) -> [[Bool]] {
  645. let codes: [[Bool]] = pixels.indices.map { indexY in
  646. pixels[0].indices.map { indexX in
  647. let pixel = pixels[indexY][indexX]
  648. return pixel.red == 0 && pixel.green == 0 && pixel.blue == 0
  649. }
  650. }
  651. return codes
  652. }
  653. // Get QRCodes from pixels
  654. private func generateCodes() -> [[Bool]]? {
  655. if let tryImageCodes = imageCodes {
  656. return tryImageCodes
  657. }
  658. func fetchPixels() -> [[Bool]]? {
  659. #if os(iOS) || os(macOS) || os(tvOS)
  660. // Get pixels from image
  661. guard let tryQRImagePixels = getPixels() else {
  662. return nil
  663. }
  664. // Get QRCodes from image
  665. return getCodes(pixels: tryQRImagePixels)
  666. #else
  667. let level = inputCorrectionLevel.qrErrorCorrectLevel
  668. if let finalContent = content {
  669. return QRCode(finalContent, errorCorrectLevel: level, withBorder: true)?.imageCodes
  670. }
  671. return nil
  672. #endif
  673. }
  674. imageCodes = fetchPixels()
  675. return imageCodes
  676. }
  677. // Special Points of QRCode
  678. private func isStatic(x: Int, y: Int, size: Int, APLPoints: [CGPoint]) -> Bool {
  679. // Empty border
  680. if x == 0 || y == 0 || x == (size - 1) || y == (size - 1) {
  681. return true
  682. }
  683. // Finder Patterns
  684. if (x <= 8 && y <= 8) || (x <= 8 && y >= (size - 9)) || (x >= (size - 9) && y <= 8) {
  685. return true
  686. }
  687. // Timing Patterns
  688. if x == 7 || y == 7 {
  689. return true
  690. }
  691. // Alignment Patterns
  692. return APLPoints.contains { point in
  693. x >= Int(point.x - 2)
  694. && x <= Int(point.x + 2)
  695. && y >= Int(point.y - 2)
  696. && y <= Int(point.y + 2)
  697. }
  698. }
  699. // Alignment Pattern Locations
  700. // http://stackoverflow.com/questions/13238704/calculating-the-position-of-qr-code-alignment-patterns
  701. private func getAlignmentPatternLocations(version: Int) -> [Int]? {
  702. if version == 1 {
  703. return nil
  704. }
  705. let divs = 2 + version / 7
  706. let size = getSize(version: version)
  707. let total_dist = size - 7 - 6
  708. let divisor = 2 * (divs - 1)
  709. // Step must be even, for alignment patterns to agree with timing patterns
  710. let step = (total_dist + divisor / 2 + 1) / divisor * 2 // Get the rounding right
  711. var coords = [6]
  712. // divs-2 down to 0, inclusive
  713. coords += ( 0...(divs - 2) ).lazy.map { i in
  714. size - 7 - (divs - 2 - i) * step
  715. }
  716. return coords
  717. }
  718. // QRCode version
  719. private func getVersion(size: Int) -> Int {
  720. return (size - 21) / 4 + 1
  721. }
  722. // QRCode size
  723. private func getSize(version: Int) -> Int {
  724. return 17 + 4 * version
  725. }
  726. /// Recommand magnification
  727. public func minMagnificationGreaterThanOrEqualTo(size: CGFloat) -> Int? {
  728. guard let codes = generateCodes() else {
  729. return nil
  730. }
  731. let finalWatermark = watermark
  732. let baseMagnification = max(1, Int(size / CGFloat(codes.count)))
  733. for offset in 0 ... 3 {
  734. let tempMagnification = baseMagnification + offset
  735. if CGFloat(Int(tempMagnification) * codes.count) >= size {
  736. if finalWatermark == nil {
  737. return tempMagnification
  738. } else if tempMagnification % 3 == 0 {
  739. return tempMagnification
  740. }
  741. }
  742. }
  743. return nil
  744. }
  745. public func maxMagnificationLessThanOrEqualTo(size: CGFloat) -> Int? {
  746. guard let codes = generateCodes() else {
  747. return nil
  748. }
  749. let finalWatermark = watermark
  750. let baseMagnification = max(1, Int(size / CGFloat(codes.count)))
  751. for offset in [0, -1, -2, -3] {
  752. let tempMagnification = baseMagnification + offset
  753. if tempMagnification <= 0 {
  754. return finalWatermark == nil ? 1 : 3
  755. }
  756. if CGFloat(tempMagnification * codes.count) <= size {
  757. if finalWatermark == nil {
  758. return tempMagnification
  759. } else if tempMagnification % 3 == 0 {
  760. return tempMagnification
  761. }
  762. }
  763. }
  764. return nil
  765. }
  766. // Calculate suitable size
  767. private func minSuitableSizeGreaterThanOrEqualTo(size: CGFloat) -> Int? {
  768. guard let codes = generateCodes() else {
  769. return nil
  770. }
  771. let baseSuitableSize = Int(size)
  772. for offset in codes.indices {
  773. let tempSuitableSize = baseSuitableSize + offset
  774. if tempSuitableSize % codes.count == 0 {
  775. return tempSuitableSize
  776. }
  777. }
  778. return nil
  779. }
  780. }