iOS 7核心图像二维码生成过于模糊。

26

这是我生成QRCode图像的代码:

+ (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];
}

结果太模糊了,能否设置生成的二维码尺寸?


你的UIImageView的contentMode是什么? - maelswarm
缩放以填充。但不确定是否相关。UIImage本身模糊。 - OMGPOP
不要使用缩放填充 - 尝试使用等比例适应或等比例填充。 - maelswarm
@Roecrew无法工作。我认为问题在于UIImage本身的大小。 - OMGPOP
11个回答

53

最简单的解决方法是将以下内容添加到您的图像视图:

Swift 5.5:

imgViewQR.layer.magnificationFilter = CALayerContentsFilter.nearest

早期版本:

imgViewQR.layer.magnificationFilter = kCAFilterNearest

感谢Ronald Hoffman的更新。

这将自动将生成的QR码图像按照imageview的大小进行最近邻插值(nearest),使图像清晰、呈像素化效果。这通常不适用于调整图标/照片的大小,但对于QR码来说是完美的

enter image description here

(在模拟器上似乎无法工作,但在真实设备上效果很好)


1
注意:如果您尝试使用CGGraphicsContext快照将此缩放版本呈现为图像,则必须将插值设置为none:https://dev59.com/questions/PmzXa4cB1Zd3GeqPZOwm#15661092 - Albert Renshaw

29

我本来打算在这个问题上悬赏,但我找到了答案。

你需要一个比例尺过滤器。要使用CoreImage实现这一点,你需要像这样做:

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

从这个答案中获得: https://dev59.com/Y3DYa4cB1Zd3GeqPFeex#16316701


6
在模拟器上很正常,但在设备上仍然模糊。 - OMGPOP
@OMGPOP,你的图片必须具有较高的分辨率(大于500x500)。 - Akshit Zaveri
仅作提醒,这可能会生成一个外观漂亮的QR码,但对于某些人来说,它可能无法被扫描。 - kevinl
这应该取决于图像的原始分辨率。对吧?@kevinl - Akshit Zaveri

14
该方法将使用CoreImage生成QR码作为CIImage。不幸的是,没有简单的方法来禁用插值,因此缩放图像将创建模糊的代码。解决方法是创建临时CGImageRef并将其绘制到灰度位图CGContextRef中。
在OSX上进行了测试,但应按原样工作于iOS。
- (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;
}

1
有一篇很棒的文章描述了一种更紧凑、更灵活的方法来完成这个任务: https://www.avanderlee.com/swift/qr-code-generation-swift/ - Dan Waylonis
@DanWaylonis 这样的话,我们应该释放 'cs' 和 'scaledImage' 吗?因为 Xcode 分析并显示了 'Potential leak of an object stored into 'scaledImage''。 - will wang

9

我遇到了同样的问题,根据这个教程,这是我解决它的方法:

-(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;
}

3

这是一个SwiftUI版本。

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)
    }
}

3

我必须改编@cromanelli的答案才能实现完美的锐度:

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
}

3

将 @Benoît Caron 的回答改写为 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
}

不错的答案,但加入一些安全解包语法会更好,谢谢。 - Sai Li

2
为了解决使用CIFilter生成的二维码模糊问题,我们需要对其进行缩放处理。但是,直接对图像进行缩放并添加到图像视图中是不可行的。因此,我们可以创建另一个CIImage,并对其进行缩放处理,然后将其分配给图像视图。
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;
}

这帮助我解决了二维码模糊的问题。

1

Swift 5.5

// 在5.5中进行了更改

imgViewQR.layer.magnificationFilter = CALayerContentsFilter.nearest

你好,Ronald。这似乎更像是对此答案的评论,而不是一个新的回答? - Eric Aya

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
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接