iOS 7 Core Image QR Code generation too blur
Asked Answered
F

11

26

here's my code for generating QRCode image

+ (UIImage *)generateQRCodeWithString:(NSString *)string {
    NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding];
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    [filter setValue:stringData forKey:@"inputMessage"];
    [filter setValue:@"M" forKey:@"inputCorrectionLevel"];
    return [UIImage imageWithCIImage:filter.outputImage];
}

The result is too blur. Is it possible to set the size of the generated qr code?

Franckot answered 13/3, 2014 at 10:1 Comment(4)
What is the contentmode of your uiimageview?Opsonize
scale to fill. but not sure whether its relevant. the uiimage itself is blurFranckot
dont use scale to fill - try aspect fit or aspect fillOpsonize
@Roecrew not working. i believe the problem is the size of uiimage itselfFranckot
S
29

I was about to start bounty on this question but i found the answer.

What you need is a scale filter. To achieve this with CoreImage, you need to do something like this:

CIImage *input = [CIImage imageWithCGImage: ImageView.Image.CGImage]; // input image is 100 X 100
CGAffineTransform transform = CGAffineTransformMakeScale(5.0f, 5.0f); // Scale by 5 times along both dimensions
CIImage *output = [input imageByApplyingTransform: transform];
// output image is now 500 X 500

FROM THIS SO ANSWER: https://mcmap.net/q/535363/-place-image-on-larger-canvas-size-using-gpu-possibly-cifilters-without-using-image-context

Standin answered 21/3, 2014 at 11:12 Comment(4)
weird. it works on the simulator, but still blur on the deviceFranckot
@OMGPOP, Your image has to be of large resolution. (More than 500x500).Standin
just a warning for some people, this might result in a nice looking QR code, but for some people, it might also not become scannable.Deerskin
That should depend on the original resolution of image. Right? @DeerskinStandin
M
53

Easiest solution is to add following to your image view:

Swift 5.5:

imgViewQR.layer.magnificationFilter = CALayerContentsFilter.nearest

Earlier versions:

imgViewQR.layer.magnificationFilter = kCAFilterNearest

(thanks to Ronald Hoffman for the update)

This will automatically upscale your generated QR code image to imageview's size using nearest which results in sharp, pixelated image. This usually isn't what you want when resizing icons/photos but is perfect for QR codes

enter image description here

(it doesn't seem to work on simulator but works great on real device)

Marco answered 7/6, 2018 at 9:13 Comment(1)
Note: If you try to render this scaled up version as an image using CGGraphicsContext snapshot, you must set the interpolation settings to none: https://mcmap.net/q/535362/-programmatic-screenshot-misrepresenting-nearest-neighborGallard
S
29

I was about to start bounty on this question but i found the answer.

What you need is a scale filter. To achieve this with CoreImage, you need to do something like this:

CIImage *input = [CIImage imageWithCGImage: ImageView.Image.CGImage]; // input image is 100 X 100
CGAffineTransform transform = CGAffineTransformMakeScale(5.0f, 5.0f); // Scale by 5 times along both dimensions
CIImage *output = [input imageByApplyingTransform: transform];
// output image is now 500 X 500

FROM THIS SO ANSWER: https://mcmap.net/q/535363/-place-image-on-larger-canvas-size-using-gpu-possibly-cifilters-without-using-image-context

Standin answered 21/3, 2014 at 11:12 Comment(4)
weird. it works on the simulator, but still blur on the deviceFranckot
@OMGPOP, Your image has to be of large resolution. (More than 500x500).Standin
just a warning for some people, this might result in a nice looking QR code, but for some people, it might also not become scannable.Deerskin
That should depend on the original resolution of image. Right? @DeerskinStandin
I
14

This method will use CoreImage to generate the QR code as a CIImage. Unfortunately, there's no simple way to disable interpolation, so scaling the image will create a blurry code. The workaround is to create temporary CGImageRef with the bits and draw it into a grayscale bitmap CGContextRef.

Tested on OSX but should work on iOS as written.

- (CGImageRef)createQRImageForString:(NSString *)string size:(CGSize)size {
  // Setup the QR filter with our string
  CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
  [filter setDefaults];

  NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
  [filter setValue:data forKey:@"inputMessage"];
  CIImage *image = [filter valueForKey:@"outputImage"];

  // Calculate the size of the generated image and the scale for the desired image size
  CGRect extent = CGRectIntegral(image.extent);
  CGFloat scale = MIN(size.width / CGRectGetWidth(extent), size.height / CGRectGetHeight(extent));

  // Since CoreImage nicely interpolates, we need to create a bitmap image that we'll draw into
  // a bitmap context at the desired size;
  size_t width = CGRectGetWidth(extent) * scale;
  size_t height = CGRectGetHeight(extent) * scale;
  CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
  CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);

#if TARGET_OS_IPHONE
  CIContext *context = [CIContext contextWithOptions:nil];
#else
  CIContext *context = [CIContext contextWithCGContext:bitmapRef options:nil];
#endif

  CGImageRef bitmapImage = [context createCGImage:image fromRect:extent];

  CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);
  CGContextScaleCTM(bitmapRef, scale, scale);
  CGContextDrawImage(bitmapRef, extent, bitmapImage);

  // Create an image with the contents of our bitmap
  CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);

  // Cleanup
  CGContextRelease(bitmapRef);
  CGImageRelease(bitmapImage);

  return scaledImage;
}
Isidoro answered 8/5, 2014 at 1:30 Comment(2)
There's a great post describing an even more compact and flexible way of doing this stuff: avanderlee.com/swift/qr-code-generation-swiftIsidoro
@DanWaylonis in this way,should we release the 'cs' and 'scaledImage'? as Xcode analylzed and showing 'Potential leak of an object stored into 'scaledImage''Underling
W
9

I ran into the same problem, based on this tutorial this is how i fixed it :

-(UIImage *) generateQRCodeWithString:(NSString *)string scale:(CGFloat) scale{
NSData *stringData = [string dataUsingEncoding:NSUTF8StringEncoding ];

CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
[filter setValue:stringData forKey:@"inputMessage"];
[filter setValue:@"M" forKey:@"inputCorrectionLevel"];

// Render the image into a CoreGraphics image
CGImageRef cgImage = [[CIContext contextWithOptions:nil] createCGImage:[filter outputImage] fromRect:[[filter outputImage] extent]];

//Scale the image usign CoreGraphics
UIGraphicsBeginImageContext(CGSizeMake([[filter outputImage] extent].size.width * scale, [filter outputImage].extent.size.width * scale));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
CGContextDrawImage(context, CGContextGetClipBoundingBox(context), cgImage);
UIImage *preImage = UIGraphicsGetImageFromCurrentImageContext();

//Cleaning up .
UIGraphicsEndImageContext();
CGImageRelease(cgImage);

// Rotate the image
UIImage *qrImage = [UIImage imageWithCGImage:[preImage CGImage]
                                            scale:[preImage scale]
                                      orientation:UIImageOrientationDownMirrored];
return qrImage;
}
Watteau answered 10/6, 2014 at 10:7 Comment(0)
D
4

Here's a SwiftUI version.

import SwiftUI
import CoreImage.CIFilterBuiltins

struct QRCodeView: View {
    let message: Data

    var body: some View {
        GeometryReader { reader in
            generate(size: reader.size)
        }
    }

    func generate(size: CGSize) -> Image {
        let filter = CIFilter.qrCodeGenerator()
        filter.message = message
        filter.correctionLevel = "Q"

        if let output = filter.outputImage {
            let x = size.width / output.extent.size.width
            let y = size.height / output.extent.size.height

            let scaled = output.transformed(by: CGAffineTransform(scaleX: x, y: y))
            if let cg = CIContext().createCGImage(scaled, from: scaled.extent) {
                return Image(uiImage: UIImage(cgImage: cg))
            }
        }

        return Image(systemName: "xmark.circle")
    }
}

struct QRCodeView_Previews: PreviewProvider {
    static var previews: some View {
        QRCodeView(message: Data("Hi mom".utf8))
            .frame(width: 200, height: 200)
    }
}
Dumfries answered 27/12, 2020 at 9:18 Comment(0)
S
3

I had to adapt @cromanelli's answer to achieve perfect sharpness :

func convertTextToQRCode(text: String, withSize size: CGSize) -> UIImage {

    let data = text.dataUsingEncoding(NSISOLatin1StringEncoding, allowLossyConversion: false)

    let filter = CIFilter(name: "CIQRCodeGenerator")!

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

    var qrcodeCIImage = filter.outputImage!

    let cgImage = CIContext(options:nil).createCGImage(qrcodeCIImage, fromRect: qrcodeCIImage.extent)
    UIGraphicsBeginImageContext(CGSizeMake(size.width * UIScreen.mainScreen().scale, size.height * UIScreen.mainScreen().scale))
    let context = UIGraphicsGetCurrentContext()
    CGContextSetInterpolationQuality(context, .None)
    CGContextDrawImage(context, CGContextGetClipBoundingBox(context), cgImage)
    let preImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    let qrCodeImage = UIImage(CGImage: preImage.CGImage!, scale: 1.0/UIScreen.mainScreen().scale, orientation: .DownMirrored)

    return qrCodeImage
}
Sympathy answered 20/5, 2016 at 7:30 Comment(0)
A
3

Rewrite @Benoît Caron's answer in Swift 3.1:

func convertTextToQRCode(text: String, withSize size: CGSize) -> UIImage {

    let data = text.data(using: String.Encoding.isoLatin1, allowLossyConversion: false)

    let filter = CIFilter(name: "CIQRCodeGenerator")!

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

    let qrcodeCIImage = filter.outputImage!

    let cgImage = CIContext(options:nil).createCGImage(qrcodeCIImage, from: qrcodeCIImage.extent)
    UIGraphicsBeginImageContext(CGSize(width: size.width * UIScreen.main.scale, height:size.height * UIScreen.main.scale))
    let context = UIGraphicsGetCurrentContext()
    context!.interpolationQuality = .none

    context?.draw(cgImage!, in: CGRect(x: 0.0,y: 0.0,width: context!.boundingBoxOfClipPath.width,height: context!.boundingBoxOfClipPath.height))

    let preImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    let qrCodeImage = UIImage(cgImage: (preImage?.cgImage!)!, scale: 1.0/UIScreen.main.scale, orientation: .downMirrored)

    return qrCodeImage
}
Alodee answered 11/4, 2017 at 9:11 Comment(1)
nice answer, but add some safely unwrapping syntax would be better, thxHowler
B
2

To fix the blur looks in QR code generated by using CIFilter: The basic idea for scaling-up the QR code image without making it blurry, is to change its transform property. But, as it’s infeasible to scale up the image and add it at the same time to the image view, we’ll just create another CIImage which will be scaled-up and then we’ll assign it to the image view.

Objective C:

- (void)createQRForData:(id)qrData forImageView:(UIImageView *)imageView {

    NSData *data = [NSJSONSerialization dataWithJSONObject:qrData options:NSJSONWritingPrettyPrinted error:nil];

    CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    [qrFilter setValue:data forKey:@"inputMessage"];
    [qrFilter setValue:@"Q" forKey: @"inputCorrectionLevel"];

    //Scaling the CIFilter image as per the UIImageView frame. 
    CGFloat scaleX = imageView.frame.size.width / qrFilter.outputImage.extent.size.width;
    CGFloat scaleY = imageView.frame.size.height / qrFilter.outputImage.extent.size.height;
    CIImage *transformedImage = [qrFilter.outputImage imageByApplyingTransform:CGAffineTransformMakeScale(scaleX, scaleY)];

    UIImage *QRImage = [UIImage imageWithCIImage:transformedImage];

    imageView.image = QRImage;
}

This helped me to resolve the blurry looks in QR code.

Bree answered 12/9, 2017 at 6:40 Comment(0)
O
1

Swift 5.5

// changed in 5.5

imgViewQR.layer.magnificationFilter = CALayerContentsFilter.nearest
Om answered 19/9, 2021 at 4:51 Comment(1)
Hello Ronald, this looks more like a comment to this answer rather than a new answer?Dutyfree
S
0

Maybe you should try this. Here is a swift tutotial.

+ (UIImage *)generateQRCodeImageBySize:(CGFloat)size andString:(NSString *)string {

    CIImage *qrCodeImage = [[CIImage alloc] init];

    NSData *stringData = [string dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:NO];

    CIFilter *ciFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];

    [ciFilter setValue:stringData forKey:@"inputMessage"];
    [ciFilter setValue:@"Q" forKey:@"inputCorrectionLevel"];

    qrCodeImage = ciFilter.outputImage;

    CGFloat scaleX = size / qrCodeImage.extent.size.width;
    CGFloat scaleY = size / qrCodeImage.extent.size.height;
    CIImage *transformedQRImage = [qrCodeImage imageByApplyingTransform:CGAffineTransformMakeScale(scaleX, scaleY)];

    return [UIImage imageWithCIImage:transformedQRImage];
}
Satellite answered 24/2, 2017 at 2:52 Comment(0)
B
0
    func generateQRCode(from string: String, imageView: UIImageView) -> UIImage? {
        let data = string.data(using: String.Encoding.ascii)
        
        if let filter = CIFilter(name: "CIQRCodeGenerator") {
            filter.setValue(data, forKey: "inputMessage")
            filter.setValue("M", forKey: "inputCorrectionLevel")
            
            if let qrImage = filter.outputImage {
                let scaleX = imageView.frame.size.width / qrImage.extent.size.width
                let scaleY = imageView.frame.size.height / qrImage.extent.size.height
                let transform = CGAffineTransform(scaleX: scaleX, y: scaleY)
                let output = qrImage.transformed(by: transform)
                return UIImage(ciImage: output)
            }
        }            
        return nil
    }
Bjorn answered 20/10, 2022 at 17:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.