QR Code Generator (CIFilter) colours for light/dark mode ?

Using the CIFilter.qrCodeGenerator() to create a QR code I wanted to change the colours dynamically to suit Light/Dark mode, but was unable to figure out how to achieve this, is it possible please ?

struct QrCodeImage {

    let context = CIContext()



    func generateQRCode(from text: String) -> UIImage {

        var qrImage = UIImage(systemName: "xmark.circle") ?? UIImage()

        let data = Data(text.utf8)

        let filter = CIFilter.qrCodeGenerator()

        filter.setValue(data, forKey: "inputMessage")



        let transform = CGAffineTransform(scaleX: 2, y: 2)

        if let outputImage = filter.outputImage?.transformed(by: transform) {

            if let image = context.createCGImage(

                outputImage,

                from: outputImage.extent) {

                qrImage = UIImage(cgImage: image)

            }

        }

        return qrImage

    }

}

Further, I cannot see an option for the different modes and assume that any colour could be used, which would be a lot better for me.

ref: https://developer.apple.com/documentation/coreimage/ciqrcodegenerator

Accepted Reply

You can use the QR code as a mask and blend it with a solid color to effectively colorize it. So you can, for example, create a black and a white version and use the register(...) method of UIImage to "bundle" them both into one dynamic image:

let qrCode = filter.outputImage!.transformed(by: transform)

// Use the QR code as a mask for blending with a color.
// Note that we need to invert the code for that, so the actual code becomes white
// and the background becomes black, because white = let color through, black = transparent.
let maskFilter = CIFilter.blendWithMask()
maskFilter.maskImage = qrCode.applyingFilter("CIColorInvert")

// create a version of the code with black foreground...
maskFilter.inputImage = CIImage(color: .black)
let blackCIImage = maskFilter.outputImage!
// ... and one with white foreground
maskFilter.inputImage = CIImage(color: .white)
let whiteCIImage = maskFilter.outputImage!

// render both images
let blackImage = context.createCGImage(blackCIImage, from: blackCIImage.extent).map(UIImage.init)!
let whiteImage = context.createCGImage(whiteCIImage, from: whiteCIImage.extent).map(UIImage.init)!

// use black version for light mode
qrImage = blackImage
// assign the white version to be used in dark mode
qrImage.imageAsset?.register(whiteImage, with: UITraitCollection(userInterfaceStyle: .dark))

return qrImage

Replies

You can use the QR code as a mask and blend it with a solid color to effectively colorize it. So you can, for example, create a black and a white version and use the register(...) method of UIImage to "bundle" them both into one dynamic image:

let qrCode = filter.outputImage!.transformed(by: transform)

// Use the QR code as a mask for blending with a color.
// Note that we need to invert the code for that, so the actual code becomes white
// and the background becomes black, because white = let color through, black = transparent.
let maskFilter = CIFilter.blendWithMask()
maskFilter.maskImage = qrCode.applyingFilter("CIColorInvert")

// create a version of the code with black foreground...
maskFilter.inputImage = CIImage(color: .black)
let blackCIImage = maskFilter.outputImage!
// ... and one with white foreground
maskFilter.inputImage = CIImage(color: .white)
let whiteCIImage = maskFilter.outputImage!

// render both images
let blackImage = context.createCGImage(blackCIImage, from: blackCIImage.extent).map(UIImage.init)!
let whiteImage = context.createCGImage(whiteCIImage, from: whiteCIImage.extent).map(UIImage.init)!

// use black version for light mode
qrImage = blackImage
// assign the white version to be used in dark mode
qrImage.imageAsset?.register(whiteImage, with: UITraitCollection(userInterfaceStyle: .dark))

return qrImage

Thanks for the code.

I had to modify the imageAsset?.register check to the following as I could not use @Environment(.colorScheme) because the Struct was not a View, as in other part of the my code, which was in SwiftUI:

var osTheme: UIUserInterfaceStyle { return UIScreen.main.traitCollection.userInterfaceStyle }
qrImage = osTheme == .light ? lightImage : darkImage

But otherwise it worked. The code that I found in the mean time was compiling but didn't work, it was a SO example using the "CIFalseColor" filter:

var qrImage = UIImage(systemName: "xmark.circle") ?? UIImage()

        let data = Data(text.utf8)
        let filter = CIFilter.qrCodeGenerator()

        filter.setValue(data, forKey: "inputMessage")

        let transform = CGAffineTransform(scaleX: 2, y: 2)

        if let outputImage = filter.outputImage?.transformed(by: transform) {

            if let image = context.createCGImage(
                outputImage,
                from: outputImage.extent) {
                qrImage = UIImage(cgImage: image)

                let colorParameters = [
                    "inputColor0": CIColor(color: UIColor.black), // Foreground
                    "inputColor1": CIColor(color: UIColor.green) // Background]

                let colored = outputImage.applyingFilter("CIFalseColor", parameters: colorParameters)

                qrImage = UIImage(ciImage: colored)
            }
        }
        return qrImage

Anyway, it does work now.

Many thanks !