ThreemaQRCodeGenerator.swift 4.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2019-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. @objc class ThreemaQRCodeGenerator: NSObject {
  22. @objc class func generate(qrString: String, dimension: Int) -> UIImage? {
  23. guard let qrFilter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
  24. let qrData = qrString.data(using: String.Encoding.ascii)
  25. qrFilter.setValue(qrData, forKey: "inputMessage")
  26. let scale = dimension/39
  27. let qrTransform = CGAffineTransform.init(scaleX: CGFloat(scale), y: CGFloat(scale))
  28. guard let qrImage = qrFilter.outputImage?.transformed(by: qrTransform) else { return nil }
  29. guard let logo = BundleUtil.imageNamed("QRLogo").cgImage else {
  30. return convert(cmage: qrImage)
  31. }
  32. guard let threemaQr: CIImage = qrImage.combined(with: CIImage(cgImage: logo)) else {
  33. return convert(cmage: qrImage)
  34. }
  35. return convert(cmage: threemaQr)
  36. }
  37. class func convert(cmage:CIImage) -> UIImage {
  38. let context:CIContext = CIContext.init(options: nil)
  39. let cgImage:CGImage = context.createCGImage(cmage, from: cmage.extent)!
  40. let image:UIImage = UIImage.init(cgImage: cgImage)
  41. return image
  42. }
  43. }
  44. extension CIImage {
  45. /// Inverts the colors and creates a transparent image by converting the mask to alpha.
  46. /// Input image should be black and white.
  47. var transparent: CIImage? {
  48. return inverted?.blackTransparent
  49. }
  50. /// Inverts the colors.
  51. var inverted: CIImage? {
  52. guard let invertedColorFilter = CIFilter(name: "CIColorInvert") else { return nil }
  53. invertedColorFilter.setValue(self, forKey: "inputImage")
  54. return invertedColorFilter.outputImage
  55. }
  56. /// Converts all black to transparent.
  57. var blackTransparent: CIImage? {
  58. guard let blackTransparentFilter = CIFilter(name: "CIMaskToAlpha") else { return nil }
  59. blackTransparentFilter.setValue(self, forKey: "inputImage")
  60. return blackTransparentFilter.outputImage
  61. }
  62. /// Applies the given color as a tint color.
  63. func tinted(using color: UIColor) -> CIImage?
  64. {
  65. guard
  66. let transparentQRImage = transparent,
  67. let filter = CIFilter(name: "CIMultiplyCompositing"),
  68. let colorFilter = CIFilter(name: "CIConstantColorGenerator") else { return nil }
  69. let ciColor = CIColor(color: color)
  70. colorFilter.setValue(ciColor, forKey: kCIInputColorKey)
  71. let colorImage = colorFilter.outputImage
  72. filter.setValue(colorImage, forKey: kCIInputImageKey)
  73. filter.setValue(transparentQRImage, forKey: kCIInputBackgroundImageKey)
  74. return filter.outputImage!
  75. }
  76. /// Combines the current image with the given image centered.
  77. func combined(with image: CIImage) -> CIImage? {
  78. guard let combinedFilter = CIFilter(name: "CISourceOverCompositing") else { return nil }
  79. let centerTransform = CGAffineTransform(translationX: extent.midX - (image.extent.size.width / 2), y: extent.midY - (image.extent.size.height / 2))
  80. combinedFilter.setValue(image.transformed(by: centerTransform), forKey: "inputImage")
  81. combinedFilter.setValue(self, forKey: "inputBackgroundImage")
  82. return combinedFilter.outputImage!
  83. }
  84. }