Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation
import CoreImage.CIFilterBuiltins
import UIKit
import WooFoundation

struct ScanToPayViewModel {
private let paymentURL: URL?
Expand All @@ -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)
}
}
12 changes: 12 additions & 0 deletions WooFoundation/WooFoundation.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -84,6 +87,9 @@
B97190D0292CF3BC0065E413 /* Result+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+Extensions.swift"; sourceTree = "<group>"; };
B987B06E284540D300C53CF6 /* CurrencyCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencyCode.swift; sourceTree = "<group>"; };
B99686DD2A13B38B00D1AF62 /* FullScreenCoverClearBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenCoverClearBackgroundView.swift; sourceTree = "<group>"; };
B99BC2112A1FAE5100E6008A /* CIImage+ImageCombination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CIImage+ImageCombination.swift"; sourceTree = "<group>"; };
B99BC2132A1FAEBC00E6008A /* URL+QRCodeGeneration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+QRCodeGeneration.swift"; sourceTree = "<group>"; };
B99BC2152A1FB21700E6008A /* UIImage+Background.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Background.swift"; sourceTree = "<group>"; };
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; };
Expand Down Expand Up @@ -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 = "<group>";
Expand Down Expand Up @@ -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 */,
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
24 changes: 24 additions & 0 deletions WooFoundation/WooFoundation/Extensions/UIImage+Background.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot happening here, so I think we could improve it by:

  • Renaming ctx to currentGraphicsContext
  • Adding internal comments
  • Changing:
-  ctx.concatenate(CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: size.height))
+ // Flips the context vertically, then translates it vertically
+ // By default, the coordinate system in Core Graphics is different from that of UIKit
+ currentGraphicsContext.scaleBy(x: 1, y: -1)
+ currentGraphicsContext.translateBy(x: 0, y: -size.height)
Suggested change
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
}
func withBackground(color: UIColor, opaque: Bool = true) -> UIImage {
UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
// Checks if there is a current graphics context, and the UIImage has a CGImage,
// return the original image otherwise
guard let currentGraphicsContext = UIGraphicsGetCurrentContext(),
let image = cgImage else {
return self
}
// Ensure removal of custom currentGraphicsContext on deallocation
defer { UIGraphicsEndImageContext() }
let rect = CGRect(origin: .zero, size: size)
currentGraphicsContext.setFillColor(color.cgColor)
currentGraphicsContext.fill(rect)
// Flips the context vertically, then translates it vertically
// By default, the coordinate system in Core Graphics is different from that of UIKit
currentGraphicsContext.scaleBy(x: 1, y: -1)
currentGraphicsContext.translateBy(x: 0, y: -size.height)
currentGraphicsContext.draw(image, in: rect)
// Return the UIImage from the current context,
// of the original image if happens to fail.
return UIGraphicsGetImageFromCurrentImageContext() ?? self
}

What do you think? I tested the changes to CGAfflineTransform and we get the same result , unless there's any other concern I've missed it seems that, from Apple docs, we shouldn't access this property directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment! I added some of the suggestions here in 0d17ba5 Some parts of the code were self-explanatory and didn't need comments, but some others were indeed abstruse

}
39 changes: 39 additions & 0 deletions WooFoundation/WooFoundation/Extensions/URL+QRCodeGeneration.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}