secant-ios-wallet/modules/Sources/Utils/QRCodeGenerator.swift

149 lines
4.9 KiB
Swift

//
// QRCodeGenerator.swift
// Zashi
//
// Created by Lukáš Korba on 04.07.2022.
//
import Foundation
import Combine
import CoreImage.CIFilterBuiltins
import SwiftUI
import Generated
public enum QRCodeGenerator {
public enum QRCodeError: Error {
case failedToGenerate
}
public enum Vendor: Equatable {
case keystone
case zashi
}
public static func generate(
from string: String,
maxPrivacy: Bool = true,
vendor: Vendor = .zashi,
color: UIColor = Asset.Colors.primary.systemColor
) -> Future<CGImage, Never> {
Future<CGImage, Never> { promise in
DispatchQueue.global().async {
guard let image = generateCode(from: string, maxPrivacy: maxPrivacy, vendor: vendor, color: color) else {
return
}
return promise(.success(image))
}
}
}
public static func generateCode(
from string: String,
scale: CGFloat = 15,
maxPrivacy: Bool = true,
vendor: Vendor = .zashi,
color: UIColor = Asset.Colors.primary.systemColor
) -> CGImage? {
let data = string.data(using: String.Encoding.utf8)
let context = CIContext()
let filter = CoreImage.CIFilter.qrCodeGenerator()
filter.setValue(data, forKey: "inputMessage")
let transform = CGAffineTransform(scaleX: scale, y: scale)
if color == .black {
guard let baseImage = filter.outputImage?.transformed(by: transform) else {
return nil
}
return QRCodeGenerator.overlayWithZecLogo(
baseImage,
context: context,
maxPrivacy: maxPrivacy,
vendor: vendor
)
} else {
guard let baseImage = filter.outputImage?.transformed(by: transform).tinted(using: color) else {
return nil
}
return QRCodeGenerator.overlayWithZecLogo(
baseImage,
context: context,
maxPrivacy: maxPrivacy,
vendor : vendor,
export: false
)
}
}
public static func overlayWithZecLogo(
_ baseImage: CIImage,
context: CIContext,
maxPrivacy: Bool,
vendor: Vendor,
export: Bool = true
) -> CGImage? {
let maxPrivacyPostfix = vendor == .zashi ? maxPrivacy ? "Max" : "Low" : ""
let vendorPrefix = vendor == .zashi ? "" : "KS_"
let filename = export ? "QROverlay" : "QRDynamicOverlay"
let overlayImageName = "\(vendorPrefix)\(filename)\(maxPrivacyPostfix)"
guard let overlayImage = UIImage(named: overlayImageName) else {
return nil
}
guard let iconCIImage = CIImage(image: overlayImage) else {
return nil
}
let ratio = 0.25
let size = baseImage.extent.width * ratio
let halfSize = size * 0.5
let iconRect = CGRect(x: baseImage.extent.width * 0.5 - halfSize, y: baseImage.extent.height * 0.5 - halfSize, width: size, height: size)
let scaleTransform = CGAffineTransform(scaleX: iconRect.size.width / iconCIImage.extent.width, y: iconRect.size.height / iconCIImage.extent.height)
let translationTransform = CGAffineTransform(translationX: iconRect.origin.x, y: iconRect.origin.y)
let transformedIconCIImage = iconCIImage.transformed(by: scaleTransform.concatenating(translationTransform))
let combinedImage = transformedIconCIImage.composited(over: baseImage)
return context.createCGImage(combinedImage, from: combinedImage.extent)
}
}
extension CIImage {
var transparent: CIImage? {
inverted?.blackTransparent
}
var inverted: CIImage? {
guard let invertedColorFilter = CIFilter(name: "CIColorInvert") else { return nil }
invertedColorFilter.setValue(self, forKey: "inputImage")
return invertedColorFilter.outputImage
}
var blackTransparent: CIImage? {
guard let blackTransparentFilter = CIFilter(name: "CIMaskToAlpha") else { return nil }
blackTransparentFilter.setValue(self, forKey: "inputImage")
return blackTransparentFilter.outputImage
}
func tinted(using color: UIColor) -> CIImage?
{
guard
let transparentQRImage = transparent,
let filter = CIFilter(name: "CIMultiplyCompositing"),
let colorFilter = CIFilter(name: "CIConstantColorGenerator") else { return nil }
let ciColor = CIColor(color: color)
colorFilter.setValue(ciColor, forKey: kCIInputColorKey)
let colorImage = colorFilter.outputImage
filter.setValue(colorImage, forKey: kCIInputImageKey)
filter.setValue(transparentQRImage, forKey: kCIInputBackgroundImageKey)
return filter.outputImage!
}
}