Osheep

时光不回头,当下最重要。

IOS原生方法实现二维码生成与扫描

  二维码的生成有好多第三方库,如Z-Xing。但是为了控制安装包的大小,或者并不需要其他的一些额外的功能,用系统的方法即可满足.

一、二维码的生成

+ (UIImage *)qrImageForString:(NSString *)string imageSize:(CGFloat)Imagesize logoImageSize:(CGFloat)waterImagesize{
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    [filter setDefaults];
    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
    [filter setValue:data forKey:@"inputMessage"];//通过kvo方式给一个字符串,生成二维码
    [filter setValue:@"H" forKey:@"inputCorrectionLevel"];//设置二维码的纠错水平,越高纠错水平越高,可以污损的范围越大
    CIImage *outPutImage = [filter outputImage];//拿到二维码图片
    return [[self alloc] createNonInterpolatedUIImageFormCIImage:outPutImage withSize:Imagesize waterImageSize:waterImagesize];
}
- (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat) size waterImageSize:(CGFloat)waterImagesize{
    CGRect extent = CGRectIntegral(image.extent);
    CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent));

    // 1.创建bitmap;
    size_t width = CGRectGetWidth(extent) * scale;
    size_t height = CGRectGetHeight(extent) * scale;
    //创建一个DeviceGray颜色空间
    CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
//CGBitmapContextCreate(void * _Nullable data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef  _Nullable space, uint32_t bitmapInfo)
//width:图片宽度像素
//height:图片高度像素
//bitsPerComponent:每个颜色的比特值,例如在rgba-32模式下为8
//bitmapInfo:指定的位图应该包含一个alpha通道。
    CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
    CIContext *context = [CIContext contextWithOptions:nil];
    //创建CoreGraphics image
    CGImageRef bitmapImage = [context createCGImage:image fromRect:extent];

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

    // 2.保存bitmap到图片
    CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);
    CGContextRelease(bitmapRef); CGImageRelease(bitmapImage);

    //原图
    UIImage *outputImage = [UIImage imageWithCGImage:scaledImage];
    //给二维码加 logo 图
    UIGraphicsBeginImageContextWithOptions(outputImage.size, NO, [[UIScreen mainScreen] scale]);
    [outputImage drawInRect:CGRectMake(0,0 , size, size)];
    //logo图
    UIImage *waterimage = [UIImage imageNamed:@"icon_imgApp"];
    //把logo图画到生成的二维码图片上,注意尺寸不要太大(最大不超过二维码图片的%30),太大会造成扫不出来
    [waterimage drawInRect:CGRectMake((size-waterImagesize)/2.0, (size-waterImagesize)/2.0, waterImagesize, waterImagesize)];
    UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newPic;
}
这个是官方文档中对inputMessage和inputCorrectionLevel的解释
  [CIQRCodeGenerator ](https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CoreImageFilterReference/#//apple_ref/doc/filter/ci/CIQRCodeGenerator) 

Generates a Quick Response code (two-dimensional barcode) from input data.
Parameters
*inputMessage*
The data to be encoded as a QR code. AnNSData
object whose display name is Message.

*inputCorrectionLevel*
A single letter specifying the error correction format. AnNSString
object whose display name is CorrectionLevel.
Default value:M

Discussion
    Generates an output image representing the input data according to the ISO/IEC 18004:2006 standard. The width and height of each module (square dot) of the code in the output image is one point. To create a QR code from a string or URL, convert it to an[NSData](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/index.html#//apple_ref/occ/cl/NSData)object using the[NSISOLatin1StringEncoding](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/index.html#//apple_ref/c/econst/NSISOLatin1StringEncoding)string encoding.
The*inputCorrectionLevel*
  parameter controls the amount of additional data encoded in the output image to provide error correction. Higher levels of error correction result in larger output images but allow larger areas of the code to be damaged or obscured without. There are four possible correction modes (with corresponding error resilience levels):
L : 7%
M : 15%
Q : 25%
H : 30%

Member Of
CICategoryBuiltIn
,CICategoryStillImage
,CICategoryGenerator
Sample Output
 **Figure 91**The result of using the CIQRCodeGenerator filter ![image: ../Art/CIQRCodeGenerator.pdf](https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CoreImageFilterReference/Art/CIQRCodeGenerator_2x.png)

  修改二维码的颜色,这一段是在网上找的,把生成的二维码图片传入,再传入想要的颜色即可
  图片要传没有加过 logo 的

- (UIImage*)imageBlackToTransparent:(UIImage*)image withRed:(CGFloat)red andGreen:(CGFloat)green andBlue:(CGFloat)blue{
    const int imageWidth = image.size.width;
    const int imageHeight = image.size.height;
    size_t bytesPerRow = imageWidth * 4;
    uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage); // 遍历像素
    int pixelNum = imageWidth * imageHeight;
    uint32_t* pCurPtr = rgbImageBuf;
    for (int i = 0; i < pixelNum; i++, pCurPtr++){
        if ((*pCurPtr & 0xFFFFFF00) < 0x99999900) // 将白色变成透明
    {
        // 改成下面的代码,会将图片转成想要的颜色
        uint8_t* ptr = (uint8_t*)pCurPtr;
        ptr[3] = red; //0~255
        ptr[2] = green;
        ptr[1] = blue;
    } else {
        uint8_t* ptr = (uint8_t*)pCurPtr;
        ptr[0] = 0;
        }
    }
    // 输出图片
    CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, nil);
    CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace, kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider, NULL, true, kCGRenderingIntentDefault);
    CGDataProviderRelease(dataProvider);
    UIImage* resultUIImage = [UIImage imageWithCGImage:imageRef]; // 清理空间
    CGImageRelease(imageRef);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    return resultUIImage;
}

二、扫码

  扫码主要用到的是AVFoundation用到的东西和相机基本相同,相机请参考我的另外一篇文章

《IOS原生方法实现二维码生成与扫描》

屏幕快照 2016-04-11 上午11.46.38.png
声明以下对象,遵守AVCaptureMetadataOutputObjectsDelegate
@interface ScanQRViewController ()<AVCaptureMetadataOutputObjectsDelegate>
//捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)
@property(nonatomic)AVCaptureDevice *device;

//AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化
@property(nonatomic)AVCaptureDeviceInput *input;

//设置输出类型为Metadata,因为这种输出类型中可以设置扫描的类型,譬如二维码
//当启动摄像头开始捕获输入时,如果输入中包含二维码,就会产生输出
@property(nonatomic)AVCaptureMetadataOutput *output;

//session:由他把输入输出结合在一起,并开始启动捕获设备(摄像头)
@property(nonatomic)AVCaptureSession *session;

//图像预览层,实时显示捕获的图像
@property(nonatomic)AVCaptureVideoPreviewLayer *previewLayer;
初始化各对象,输入输出设备结合
- (void)creatCaptureDevice{
    //使用AVMediaTypeVideo 指明self.device代表视频,默认使用后置摄像头进行初始化
    self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    //使用设备初始化输入
    self.input = [[AVCaptureDeviceInput alloc]initWithDevice:self.device error:nil];

    //生成输出对象
    self.output = [[AVCaptureMetadataOutput alloc]init];

    //设置代理,一旦扫描到指定类型的数据,就会通过代理输出
    //在扫描的过程中,会分析扫描的内容,分析成功后就会调用代理方法在队列中输出
    [self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

    //生成会话,用来结合输入输出
    self.session = [[AVCaptureSession alloc]init];
    if ([self.session canAddInput:self.input]) {
        [self.session addInput:self.input];
    }
    if ([self.session canAddOutput:self.output]) {
        [self.session addOutput:self.output];
    }

    //指定当扫描到二维码的时候,产生输出
    //AVMetadataObjectTypeQRCode 指定二维码
    //指定识别类型一定要放到添加到session之后
    [self.output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
//设置扫描信息的识别区域,左上角为(0,0),右下角为(1,1),不设的话全屏都可以识别。设置过之后可以缩小信息扫描面积加快识别速度。
//这个属性并不好设置,整了半天也没太搞明白,到底x,y,width,height,怎么是对应的,这是我一点一点试的扫描区域,看不到只能调一下,扫一扫试试
    [self.output setRectOfInterest:CGRectMake(0.1 ,0.3 , 0.4, 0.4)];
    //使用self.session,初始化预览层,self.session负责驱动input进行信息的采集,layer负责把图像渲染显示
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session];
    self.previewLayer.frame = CGRectMake(0, 0, kScreenWidth , kScreenHeight);
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    [self.view.layer addSublayer:self.previewLayer];

    //开始启动
    [self.session startRunning];
}
实现代理方法
#pragma mark 输出的代理
//metadataObjects :把识别到的内容放到该数组中
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    //停止扫描
    [self.session stopRunning];
    [self.timer invalidate];
    self.timer = nil;
    [self.lineView removeFromSuperview];
    if ([metadataObjects count] >= 1) {
        //数组中包含的都是AVMetadataMachineReadableCodeObject 类型的对象,该对象中包含解码后的数据
        AVMetadataMachineReadableCodeObject *qrObject = [metadataObjects lastObject];
        拿到扫描内容在这里进行个性化处理
        NSLog(@"识别成功%@",qrObject.stringValue);
    }
}

三、遇到的问题和解决办法

(1)二维码上加logo图的时候,图片很模糊,这是由于UIGraphicsBeginImageContextWithOptions里的 scale 造成的,由于 iPhone 的屏幕都是retina屏幕,都是2倍,3倍像素,这里的 scale 要根据屏幕来设置 即[[UIScreen mainScreen] scale]这样图片就会很清晰
(2)setRectOfInterest:设置扫描信息的识别区域,左上角为(0,0),右下角为(1,1),不设的话全屏都可以识别。设置过之后可以缩小信息扫描面积加快识别速度,原来扫描的是整个屏幕的大小,这时候只扫描一块区域,以此加快识别速度。但是这个属性并不好设置,整了半天也没太搞明白,到底x,y,width,height,怎么是对应的,而且是比例不是直接的数字,我是一点一点试的扫描区域,看不到情况,只能调一下,扫一扫试试
最后也没整明白,哪位大神知道,求解答
附上官方解释:
The value of this property is a CGRect that determines the receiver’s rectangle of interest for each frame of video.
The rectangle’s origin is top left and is relative to the coordinate space of the device providing the metadata. Specifying
a rectOfInterest may improve detection performance for certain types of metadata. The default value of this property is the
value CGRectMake(0, 0, 1, 1). Metadata objects whose bounds do not intersect with the rectOfInterest will not be returned.

点赞