Reputation: 6550
I am experiencing a problem using CIQRCodeGenerator
to create a QR code: when I generate the code at first, it is crisp, but when I run the function again, with the same input, the QR code becomes blurry:
Initial Run (more clear):
Second Run (more blurry):
The following function is first called in viewWillAppear
and subsequently triggered after the user taps a button.
func generateQRCodeFromString(string: String) -> UIImage? {
let data = string.dataUsingEncoding(NSISOLatin1StringEncoding)
if let filter = CIFilter(name: "CIQRCodeGenerator") {
filter.setValue(data, forKey: "inputMessage")
filter.setValue("H", forKey: "inputCorrectionLevel")
let transform = CGAffineTransformMakeScale(10, 10)
if let output = filter.outputImage?.imageByApplyingTransform(transform) {
return UIImage(CIImage: output)
}
}
return nil
}
A sample project illustrating the problem is available here: http://jakeserver.com/Uploads/Apps/QR_Test.zip
Is there a reason why the UIImage becomes blurry after the function is run a second time with the same input?
EDIT - Added More Information
override func viewDidLoad() {
super.viewDidLoad()
qrCode.image = generateQRCodeFromString("test", size: qrCode.frame.size);
}
override func viewWillLayoutSubviews() {
qrCodeWidth.constant = self.view.frame.width * 0.8;
}
@IBAction func buttonTapped(sender: AnyObject) {
qrCode.image = generateQRCodeFromString("test", size: qrCode.frame.size);
}
Upvotes: 2
Views: 958
Reputation: 3342
Updated version of Aaron's great answer, for Swift 5+
func generateQRCodeFromString(string: String, size: CGSize) -> UIImage? {
guard let data = string.data(using: .isoLatin1),
let filter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
filter.setDefaults()
filter.setValue(data, forKey: "inputMessage")
filter.setValue("H", forKey: "inputCorrectionLevel")
guard let image = filter.outputImage else { return nil }
let extent = CGRectIntegral(image.extent)
let scale = min(size.width / extent.width, size.height / extent.height);
let (height, width) = (extent.height * scale, extent.width * scale)
let colorSpace = CGColorSpaceCreateDeviceGray()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
guard let bitmapContext = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else { return nil }
bitmapContext.interpolationQuality = CGInterpolationQuality.none
bitmapContext.scaleBy(x: CGFloat(scale), y: CGFloat(scale))
bitmapContext.draw(CIContext().createCGImage(image, from: extent)!, in: extent)
if let scaledImage = bitmapContext.makeImage() {
return UIImage(cgImage: scaledImage)
}
return nil
}
Upvotes: 1
Reputation: 66234
I'm not sure why the blurriness changes between runs (maybe an internal implementation detail), but in Objective-C code I worked around this by making the QR code and then manually writing the image into a larger sized bitmap context.
I took a stab at porting that code to Swift and came up with this:
func generateQRCodeFromString(string: String, size: CGSize) -> UIImage? {
guard let data = string.dataUsingEncoding(NSISOLatin1StringEncoding),
let filter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
filter.setDefaults()
filter.setValue(data, forKey: "inputMessage")
filter.setValue("H", forKey: "inputCorrectionLevel")
guard let image = filter.outputImage else { return nil }
let extent = CGRectIntegral(image.extent)
let scale = min(size.width / extent.width, size.height / extent.height);
let (height, width) = (extent.height * scale, extent.width * scale)
let colorSpace = CGColorSpaceCreateDeviceGray()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.None.rawValue)
guard let bitmapContext = CGBitmapContextCreate(nil, Int(width), Int(height), 8, 0, colorSpace, bitmapInfo.rawValue) else { return nil }
CGContextSetInterpolationQuality(bitmapContext, CGInterpolationQuality.None)
CGContextScaleCTM(bitmapContext, CGFloat(scale), CGFloat(scale))
CGContextDrawImage(bitmapContext, extent, CIContext().createCGImage(image, fromRect: extent))
if let scaledImage = CGBitmapContextCreateImage(bitmapContext) {
return UIImage(CGImage: scaledImage)
// You might need to use this instead:
// return UIImage(CGImage: <#T##CGImage#>, scale: <#T##CGFloat#>, orientation: <#T##UIImageOrientation#>)
}
return nil
}
Will that work for your use case?
BTW, I don't think this caused your issue, but you weren't unwrapping data
(dataUsingEncoding(_:)
returns NSData?
not NSData
).
Upvotes: 2