From 97b116f61a1ca2cc44073797307e5eae563ea5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Thu, 25 May 2023 17:16:40 +0200 Subject: [PATCH 1/4] Add logo to the Scan to Pay QR Code --- .../Orders/ScanToPay/ScanToPayViewModel.swift | 19 +++------ .../WooFoundation.xcodeproj/project.pbxproj | 12 ++++++ .../Extensions/CIImage+ImageCombination.swift | 17 ++++++++ .../Extensions/UIImage+Background.swift | 23 +++++++++++ .../Extensions/URL+QRCodeGeneration.swift | 39 +++++++++++++++++++ 5 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 WooFoundation/WooFoundation/Extensions/CIImage+ImageCombination.swift create mode 100644 WooFoundation/WooFoundation/Extensions/UIImage+Background.swift create mode 100644 WooFoundation/WooFoundation/Extensions/URL+QRCodeGeneration.swift diff --git a/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayViewModel.swift b/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayViewModel.swift index 3d6401956c9..e8e9e3b3b30 100644 --- a/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayViewModel.swift +++ b/WooCommerce/Classes/ViewRelated/Orders/ScanToPay/ScanToPayViewModel.swift @@ -1,6 +1,6 @@ import Foundation -import CoreImage.CIFilterBuiltins import UIKit +import WooFoundation struct ScanToPayViewModel { private let paymentURL: URL? @@ -10,19 +10,12 @@ struct ScanToPayViewModel { } func generateQRCodeImage() -> UIImage? { - guard let paymentURLString = paymentURL?.absoluteString else { - return nil + guard let logoImage = UIImage + .wooLogoImage()? + .withBackground(color: .black) else { + return paymentURL?.generateQRCode() } - let context = CIContext() - let filter = CIFilter.qrCodeGenerator() - filter.message = Data(paymentURLString.utf8) - - guard let outputImage = filter.outputImage, - let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { - return nil - } - - return UIImage(cgImage: cgImage) + return paymentURL?.generateQRCode(combinedWith: logoImage) } } diff --git a/WooFoundation/WooFoundation.xcodeproj/project.pbxproj b/WooFoundation/WooFoundation.xcodeproj/project.pbxproj index fe37c3c7442..f2c58ec8e8b 100644 --- a/WooFoundation/WooFoundation.xcodeproj/project.pbxproj +++ b/WooFoundation/WooFoundation.xcodeproj/project.pbxproj @@ -35,6 +35,9 @@ B97190D1292CF3BC0065E413 /* Result+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B97190D0292CF3BC0065E413 /* Result+Extensions.swift */; }; B987B06F284540D300C53CF6 /* CurrencyCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B987B06E284540D300C53CF6 /* CurrencyCode.swift */; }; B99686DE2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99686DD2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift */; }; + B99BC2122A1FAE5100E6008A /* CIImage+ImageCombination.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99BC2112A1FAE5100E6008A /* CIImage+ImageCombination.swift */; }; + B99BC2142A1FAEBC00E6008A /* URL+QRCodeGeneration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99BC2132A1FAEBC00E6008A /* URL+QRCodeGeneration.swift */; }; + B99BC2162A1FB21700E6008A /* UIImage+Background.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99BC2152A1FB21700E6008A /* UIImage+Background.swift */; }; B9C9C63F283E703C001B879F /* WooFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9C9C635283E703C001B879F /* WooFoundation.framework */; }; B9C9C659283E7195001B879F /* NSDecimalNumber+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C9C658283E7195001B879F /* NSDecimalNumber+Helpers.swift */; }; B9C9C65D283E71C8001B879F /* CurrencyFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9C9C65B283E71C8001B879F /* CurrencyFormatter.swift */; }; @@ -84,6 +87,9 @@ B97190D0292CF3BC0065E413 /* Result+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extensions.swift"; sourceTree = ""; }; B987B06E284540D300C53CF6 /* CurrencyCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyCode.swift; sourceTree = ""; }; B99686DD2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCoverClearBackgroundView.swift; sourceTree = ""; }; + B99BC2112A1FAE5100E6008A /* CIImage+ImageCombination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CIImage+ImageCombination.swift"; sourceTree = ""; }; + B99BC2132A1FAEBC00E6008A /* URL+QRCodeGeneration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+QRCodeGeneration.swift"; sourceTree = ""; }; + B99BC2152A1FB21700E6008A /* UIImage+Background.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Background.swift"; sourceTree = ""; }; B9AED558283E7553002A2668 /* Yosemite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Yosemite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B9AED55B283E755A002A2668 /* Hardware.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Hardware.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B9C9C635283E703C001B879F /* WooFoundation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WooFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -252,6 +258,9 @@ 68FBC5B228926B2C00A05461 /* Collection+Extensions.swift */, 03B8C3882914083F002235B1 /* Bundle+Woo.swift */, B97190D0292CF3BC0065E413 /* Result+Extensions.swift */, + B99BC2112A1FAE5100E6008A /* CIImage+ImageCombination.swift */, + B99BC2132A1FAEBC00E6008A /* URL+QRCodeGeneration.swift */, + B99BC2152A1FB21700E6008A /* UIImage+Background.swift */, ); path = Extensions; sourceTree = ""; @@ -481,7 +490,9 @@ buildActionMask = 2147483647; files = ( 03B8C3892914083F002235B1 /* Bundle+Woo.swift in Sources */, + B99BC2122A1FAE5100E6008A /* CIImage+ImageCombination.swift in Sources */, B9C9C659283E7195001B879F /* NSDecimalNumber+Helpers.swift in Sources */, + B99BC2162A1FB21700E6008A /* UIImage+Background.swift in Sources */, 26AF1F5328B8362800937BA9 /* UIColor+SemanticColors.swift in Sources */, 03597A9B28F87BFC005E4A98 /* WooCommerceComUTMProvider.swift in Sources */, B9C9C663283E7296001B879F /* Logging.swift in Sources */, @@ -502,6 +513,7 @@ 26AF1F5528B8362800937BA9 /* UIColor+ColorStudio.swift in Sources */, 26AF1F5428B8362800937BA9 /* ColorStudio.swift in Sources */, 68FBC5B328926B2C00A05461 /* Collection+Extensions.swift in Sources */, + B99BC2142A1FAEBC00E6008A /* URL+QRCodeGeneration.swift in Sources */, B99686DE2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/WooFoundation/WooFoundation/Extensions/CIImage+ImageCombination.swift b/WooFoundation/WooFoundation/Extensions/CIImage+ImageCombination.swift new file mode 100644 index 00000000000..ce0e0469a74 --- /dev/null +++ b/WooFoundation/WooFoundation/Extensions/CIImage+ImageCombination.swift @@ -0,0 +1,17 @@ +import CoreImage.CIFilterBuiltins + +extension CIImage { + /// Combines the current image with the given image centered. + /// + func combined(with image: CIImage) -> CIImage? { + guard let combinedFilter = CIFilter(name: "CISourceOverCompositing") else { + return nil + } + + let centerTransform = CGAffineTransform(translationX: extent.midX - (image.extent.size.width / 2), y: extent.midY - (image.extent.size.height / 2)) + combinedFilter.setValue(image.transformed(by: centerTransform), forKey: "inputImage") + combinedFilter.setValue(self, forKey: "inputBackgroundImage") + + return combinedFilter.outputImage + } +} diff --git a/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift b/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift new file mode 100644 index 00000000000..bc18d895424 --- /dev/null +++ b/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift @@ -0,0 +1,23 @@ +import UIKit + +public extension UIImage { + /// Adds a background color to the given UIImage, setting also whether it should be opaque or not + func withBackground(color: UIColor, opaque: Bool = true) -> UIImage { + UIGraphicsBeginImageContextWithOptions(size, opaque, scale) + + guard let ctx = UIGraphicsGetCurrentContext(), + let image = cgImage else { + return self + } + + defer { UIGraphicsEndImageContext() } + + let rect = CGRect(origin: .zero, size: size) + ctx.setFillColor(color.cgColor) + ctx.fill(rect) + ctx.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: size.height)) + ctx.draw(image, in: rect) + + return UIGraphicsGetImageFromCurrentImageContext() ?? self + } +} diff --git a/WooFoundation/WooFoundation/Extensions/URL+QRCodeGeneration.swift b/WooFoundation/WooFoundation/Extensions/URL+QRCodeGeneration.swift new file mode 100644 index 00000000000..d4422a09c73 --- /dev/null +++ b/WooFoundation/WooFoundation/Extensions/URL+QRCodeGeneration.swift @@ -0,0 +1,39 @@ +import Foundation +import UIKit + +public extension URL { + /// Returns a black and white QR UIImage code for this URL. + /// + func generateQRCode() -> UIImage? { + guard let outputImage = generateQRCodeCIImage(), + let cgImage = CIContext().createCGImage(outputImage, from: outputImage.extent) else { + return nil + } + + return UIImage(cgImage: cgImage) + } + + + /// Returns a black and white QR code for this URL, adding the passed image centered. + /// + func generateQRCode(combinedWith image: UIImage) -> UIImage? { + guard let outputImage = generateQRCodeCIImage(), + let cgLogoImage = image.cgImage, + let combinedImage = outputImage.combined(with: CIImage(cgImage: cgLogoImage)), + let cgImage = CIContext().createCGImage(combinedImage, from: combinedImage.extent) else { + return nil + } + + return UIImage(cgImage: cgImage) + } + + /// Returns a black and white QR CIImage code for this URL. + /// + private func generateQRCodeCIImage() -> CIImage? { + let filter = CIFilter.qrCodeGenerator() + filter.message = Data(absoluteString.utf8) + + let qrTransform = CGAffineTransform(scaleX: 12, y: 12) + return filter.outputImage?.transformed(by: qrTransform) + } +} From 72b2b73fb10d9f15031d6b17eb3f296234eb8091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Thu, 25 May 2023 17:23:10 +0200 Subject: [PATCH 2/4] Add empty lines for better readability --- .../WooFoundation/Extensions/UIImage+Background.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift b/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift index bc18d895424..89d767e0348 100644 --- a/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift +++ b/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift @@ -2,6 +2,7 @@ import UIKit public extension UIImage { /// Adds a background color to the given UIImage, setting also whether it should be opaque or not + /// func withBackground(color: UIColor, opaque: Bool = true) -> UIImage { UIGraphicsBeginImageContextWithOptions(size, opaque, scale) @@ -9,7 +10,7 @@ public extension UIImage { let image = cgImage else { return self } - + defer { UIGraphicsEndImageContext() } let rect = CGRect(origin: .zero, size: size) From 0d17ba5857ffb62292990fb5c0801de4ad120cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 26 May 2023 09:38:58 +0200 Subject: [PATCH 3/4] Add format, better naming, and comments --- .../Extensions/UIImage+Background.swift | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift b/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift index 89d767e0348..e12c7fb75f0 100644 --- a/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift +++ b/WooFoundation/WooFoundation/Extensions/UIImage+Background.swift @@ -2,23 +2,27 @@ import UIKit public extension UIImage { /// Adds a background color to the given UIImage, setting also whether it should be opaque or not - /// + /// func withBackground(color: UIColor, opaque: Bool = true) -> UIImage { - UIGraphicsBeginImageContextWithOptions(size, opaque, scale) + UIGraphicsBeginImageContextWithOptions(size, opaque, scale) - guard let ctx = UIGraphicsGetCurrentContext(), - let image = cgImage else { - return self - } + guard let currentContext = UIGraphicsGetCurrentContext(), + let image = cgImage else { + return self + } - defer { UIGraphicsEndImageContext() } + defer { UIGraphicsEndImageContext() } - let rect = CGRect(origin: .zero, size: size) - ctx.setFillColor(color.cgColor) - ctx.fill(rect) - ctx.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: size.height)) - ctx.draw(image, in: rect) + let rect = CGRect(origin: .zero, size: size) + currentContext.setFillColor(color.cgColor) + currentContext.fill(rect) - return UIGraphicsGetImageFromCurrentImageContext() ?? self + // Because the coordinate system in Core Graphics is different from that of UIKit, + // we need to flip the context vertically, and then translate it vertically + currentContext.scaleBy(x: 1, y: -1) + currentContext.translateBy(x: 0, y: -size.height) + currentContext.draw(image, in: rect) + + return UIGraphicsGetImageFromCurrentImageContext() ?? self } } From 57d7200e10a89f3d6de5d34e152849e84eaa21fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81sar=20Vargas=20Casaseca?= Date: Fri, 26 May 2023 09:46:57 +0200 Subject: [PATCH 4/4] Add Release note --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index b2d34e29d5e..3770535b029 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -11,6 +11,7 @@ - [*] My Store: A new button to share the current store is added on the top right of the screen. [https://github.com/woocommerce/woocommerce-ios/pull/9796] - [*] Mobile Payments: The screen brightness is increased when showing the Scan to Pay view so the QR code can be scanned more easily [https://github.com/woocommerce/woocommerce-ios/pull/9807] +- [*] Mobile Payments: The Woo logo is added to the QR code on the Scan to Pay screen [https://github.com/woocommerce/woocommerce-ios/pull/9823] 13.7 -----