07.二维码图片生成,识别二维码图片,扫描二维码

@(〓〓 iOS-实用技术)[二维码的使用]


目录

  • 07.二维码图片生成,识别二维码图片,扫描二维码
  • 1.二维码简介
    • 二维码使用场景
  • 2.生成二维码图片
    • 生成二维码的基本步骤
    • 生成二维码的实现参考代码
  • 3.识别二维码图片
    • 识别二维码基本步骤
    • 识别二维码图片参考代码
  • 4.扫描二维码
    • 扫描二维码基本步骤
    • 扫描二维码核心参考代码
  • 5.封装二维码工具类
    • 工具类的接口头文件QRCodeTool.h
    • 工具类的功能实现QRCodeTool.m

1.二维码简介

从iOS7开始集成了二维码的生成和读取功能,> 此前被广泛使用的zbarsdk目前不支持64位处理器,所以从2015年2月1号起, 不允许不支持64位处理器的APP上架.

二维码使用场景

  • 1.二维码使用场景
    • 信息获取(名片、WIFI密码、资料)
    • 手机电商(用户扫码、手机直接购物下单)
    • 加好友(QQ, 微信, 扫一扫加好友)
    • 手机支付(扫描商品二维码,通过银行或第三方支付提供的手机端通道完成支付)

2.生成二维码图片

生成二维码的基本步骤

  • 1.实例化二维码滤镜

  • 2.恢复滤镜的默认属性

  • 3.将字符串转换成NSData

  • 4.通过KVC设置滤镜inputMessage数据

    • 通过KVC设置滤镜的 inputCorrectionLevel (容错率)
  • 5.获得滤镜输出的图像

  • 6.将CIImage转换成UIImage,并放大显示

  • 7.通过位图创建高清图片

/**
 *  根据CIImage生成指定大小的UIImage
 *
 *  @param image CIImage
 *  @param size  图片宽度
 */
+ (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat) size
{
    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;
    CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
    CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
    CIContext *context = [CIContext contextWithOptions:nil];
    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);
    return [UIImage imageWithCGImage:scaledImage];
}

生成二维码的实现参考代码


#pragma mark - 生成二维码图片
/**
 *  根据外界传递过来的内容, 生成一个二维码图片, 并且, 可以根据参数, 添加小头像,在生成后的二维码中间
 *
 *  @param content        二维码内容
 *  @param bigImageSize   大图片的尺寸
 *  @param smallImage     小图片
 *  @param smallImageSize 小图片的尺寸
 *
 *  @return 合成后的二维码图片
 */
+ (UIImage *)imageQRCodeWithContent:(NSString *)content bigImageSize:(CGFloat)bigImageSize smallImage:(UIImage *)smallImage smallImageSize:(CGFloat)smallImageSize
{
    // 1.将要生成的内容转码为UTF8编码
    NSData *strData = [content dataUsingEncoding:NSUTF8StringEncoding];
    
    // 1.创建一个二维码滤镜
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    
    // 1.1 恢复滤镜默认设置
    [filter setDefaults];
    
    // 2.设置滤镜的输入内容
    // 如果要给滤镜设置输入数据,只能使用KVC设置. key: inputMessage
    // 输入的数据只能传递NSData
    [filter setValue:strData forKey:@"inputMessage"];
    
    // 2.1 设置二维码的纠错率 key: inputCorrectionLevel
    // 纠错率等级: L, M, Q, H
    [filter setValue:@"H" forKey:@"inputCorrectionLevel"];
    
    // 3.直接从二维码滤镜中获取需要的二维码图片
    CIImage *image = [filter outputImage];
    
    // 3.1 默认生成的二维码尺寸为 23x23 ,需要借助位图来处理方法图片, 获取一个高清的图片
    UIImage *newImage = [self createNonInterpolatedUIImageFormCIImage:image withSize:bigImageSize];
    
    // 3.2 判断是否有小图标,如果有小图标,合成小图标
    if (smallImage != nil) {
        newImage = [self createImageBigImage:newImage smallImage:smallImage sizeWH:smallImageSize];
    }
    
    return newImage;
}

/**
 *  根据两个图片,合成一个大图片
 *
 *  @param bigImage   大图的背景图片
 *  @param smallImage 小图标(居中)
 *  @param sizeWH     小图标的尺寸
 *
 *  @return 合成后的图片
 */
+ (UIImage *)createImageBigImage:(UIImage *)bigImage smallImage:(UIImage *)smallImage sizeWH:(CGFloat)sizeWH
{
    CGSize size = bigImage.size;
    
    // 1.开启一个图形山下文
    UIGraphicsBeginImageContext(size);
    
    // 2.绘制大图片
    [bigImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
    
    // 3.绘制小图片
    CGFloat x = (size.width - sizeWH) * 0.5;
    CGFloat y = (size.height - sizeWH) *0.5;
    [smallImage drawInRect:CGRectMake(x, y, sizeWH, sizeWH)];
    
    // 4.取出合成图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 5.关闭图形上下文
    UIGraphicsEndImageContext();
    
    return newImage;
}

/**
 *  根据CIImage生成指定大小的UIImage
 *
 *  @param image CIImage
 *  @param size  图片宽度
 */
+ (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat) size
{
    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;
    CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
    CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
    CIContext *context = [CIContext contextWithOptions:nil];
    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);
    return [UIImage imageWithCGImage:scaledImage];
}
  • 生成二维码实现效果图
01.生成二维码实现效果图.gif

3.识别二维码图片

识别二维码基本步骤

  • 1.创建一个上下文

  • 2.创建一个探测器

  • 3.转换原图片为 CIImage

  • 4.获取探测器识别的图像特征

  • 5.遍历图片特征, 获取数据

  • 6.绘制识别到的二维码边框

  • 7.传递识别的数据给外界.


识别二维码图片参考代码

#pragma mark - 识别二维码图片

/**
 *  识别一个图片中所有的二维码, 获取二维码内容
 *
 *  @param sourceImage       需要识别的图片
 *  @param isDrawWRCodeFrame 是否绘制识别到的边框
 *  @param completeBlock     (识别出来的结果数组, 识别出来的绘制二维码图片)
 */
+ (void)detectorQRCodeImageWithSourceImage:(UIImage *)sourceImage isDrawWRCodeFrame:(BOOL)isDrawWRCodeFrame completeBlock:(void(^)(NSArray *resultArray, UIImage *resultImage))completeBlock
{
    // 0.创建上下文
    CIContext *context = [[CIContext alloc] init];
    // 1.创建一个探测器
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}];
    
    // 2.直接开始识别图片,获取图片特征
    CIImage *imageCI = [[CIImage alloc] initWithImage:sourceImage];
    NSArray<CIFeature *> *features = [detector featuresInImage:imageCI];
    
    // 3.读取特征
    UIImage *tempImage = sourceImage;
    NSMutableArray *resultArray = [NSMutableArray array];
    for (CIFeature *feature in features) {
        
        CIQRCodeFeature *tempFeature = (CIQRCodeFeature *)feature;
        
        [resultArray addObject:tempFeature.messageString];
        
        if (isDrawWRCodeFrame) {
            tempImage = [self drawQRCodeFrameFeature:tempFeature toImage:tempImage];
        }
    }
    
    // 4.使用block传递数据给外界
    completeBlock(resultArray, tempImage);
    
}

/**
 *  根据一个特征, 对给定图片, 进行绘制边框
 *
 *  @param feature 特征对象
 *  @param toImage 需要绘制的图片
 *
 *  @return 绘制好边框的图片
 */
+ (UIImage *)drawQRCodeFrameFeature:(CIQRCodeFeature *)feature toImage:(UIImage *)toImage
{
    // bounds,相对于原图片的一个大小
    // 坐标系是以左下角为(0, 0)
    CGRect bounds = feature.bounds;
    
    CGSize size = toImage.size;
    // 1.开启图形上下文
    UIGraphicsBeginImageContext(size);
    
    // 2.绘制图片
    [toImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
    
    // 3.反转上下文坐标系
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextScaleCTM(context, 1, -1);
    CGContextTranslateCTM(context, 0, -size.height);
    
    // 4.绘制边框
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:bounds];
    path.lineWidth = 12;
    [[UIColor redColor] setStroke];
    [path stroke];
    
    // 4.取出图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 5.关闭上下文
    UIGraphicsEndImageContext();
    
    return newImage;
}

  • 识别二维码图片运行效果图(注意: 识别二维码完成后,生成的二维码有带红色边框)
02.识别二维码图片.gif

4.扫描二维码

扫描二维码基本步骤

  • 1.实例化拍摄设备

  • 2.设置输入设备

  • 3.设置元数据输出处理对象

    • 3.1 实例化拍摄元数据输出
    • 3.2 设置输出数据代理
  • 4.添加拍摄会话

  • 5.视频预览图层(不是必须)

  • 6.启动会话

  • 7.监听元数据处理后的结果

扫描二维码核心参考代码


#pragma mark - 扫描二维码

/**
 *  启动二维码扫描
 *
 *  @param inView      显示视频预览的view
 *  @param resultBlock 扫描结果回调
 */
- (void)startScanQRCodeInView:(UIView *)inView resultBlock:(void(^)(NSArray<NSString *> *strArray))resultBlock
{
    // 1.记录block,在核实的地方执行
    self.scanResultBlock = resultBlock;
    
    // 2.在天津之前,先判断当前会话是否可以添加
    if ([self.session canAddInput:self.input] &&[self.session canAddOutput:self.output]) {
        
        [self.session addInput:self.input];
        [self.session addOutput:self.output];
        
        // 设置元数据处理的结果类型
        // 如果只需要处理二维码, 那么只需要把处理类型改为二维码类型就可以
        // output.availableMetadataObjectTypes
        // 这个设置, 一定要在会话添加输出处理之后, 才能设置, 否则, 扫描不到, 无法处理
        self.output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];
    }
    
    // 3.1 添加一个视频预览图层
    self.preLayer.frame = inView.bounds;
    
    NSArray *subLayers = inView.layer.sublayers;
    if (subLayers.count == 0) {
        [inView.layer insertSublayer:self.preLayer atIndex:0];
        return;
    }
    
    if (![subLayers containsObject:self.preLayer]) {
        [inView.layer insertSublayer:self.preLayer atIndex:0];
    }
    
    // 4.开始扫描(启动会话)
    [self.session startRunning];
    
}

/**
 *  设置扫描识别的区域
 *
 *  @param sourceFrame 扫描区域
 */
- (void)setInterstRect:(CGRect)sourceFrame
{
    
    // 3.2 设置扫描识别的区域
    // 坐标系: 0, 0 是右上角
    // 横屏状态下的坐标
    CGRect bounds = [UIScreen mainScreen].bounds;
    CGFloat x = sourceFrame.origin.x / bounds.size.width;
    CGFloat y = sourceFrame.origin.y / bounds.size.height;
    CGFloat w = sourceFrame.size.width / bounds.size.width;
    CGFloat h = sourceFrame.size.height / bounds.size.height;
    
    self.output.rectOfInterest = CGRectMake(y, x, h, w);
}

// 移除二维码边框
- (void)removeQRCodeFrame
{
    NSArray *subLayers = self.preLayer.sublayers;
    if (subLayers.count == 0) {
        return;
    }
    
    for (CALayer* layer in subLayers) {
        
        if ([layer isKindOfClass:[CAShapeLayer class]]) {
            [layer removeFromSuperlayer];
        }
    }
}

// 绘制二维码边框
- (void)drawQRCodeFrameWithObj:(AVMetadataMachineReadableCodeObject *)obj
{
    
    //        corners, 是二维码的四个角, 但是坐标如果想要使用, 需要进行转换
    //         print(obj.corners)
    
    // 1. 必须使用视频预览图层, 对坐标进行转换
    AVMetadataMachineReadableCodeObject *resultObj = [self.preLayer transformedMetadataObjectForMetadataObject:obj];
    
    // 根据四个点,绘制一个曲线
    CAShapeLayer *layer = [[CAShapeLayer alloc] init];
    layer.strokeColor = [UIColor redColor].CGColor;
    layer.fillColor = [UIColor clearColor].CGColor;
    layer.lineWidth = 6;
    
    UIBezierPath *path = [[UIBezierPath alloc] init];
    
    // 对四个点的数据进行遍历, 并且根据里面的字典, 创建对应的point
    NSInteger pointCount = resultObj.corners.count;
    for (int i = 0; i < pointCount; i++) {
        
        CFDictionaryRef pointDict = (__bridge CFDictionaryRef)resultObj.corners[i];
        
        CGPoint point = CGPointZero;
        CGPointMakeWithDictionaryRepresentation(pointDict, &point);
        
        // 绘制贝塞尔曲线(二维码边框)
        if (i == 0) {
            [path moveToPoint:point];
        } else {
            [path addLineToPoint:point];
        }
        
    }
    
    [path closePath];
    
    layer.path = path.CGPath;
    
    // 添加形状图层到需要展示的图层上面
    [self.preLayer addSublayer:layer];
}

#pragma mark - AVCaptureMetadataOutputObjectsDelegate代理

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    // 移除之前的边框
    [self removeQRCodeFrame];
    
    NSMutableArray *resultStrs = [NSMutableArray array];
    // 以后, 如果扫描到其他码制, 也会调用这个方法, 所以, 在这里, 如果我们只是处理二维码, 需要对元数据数据类型, 进行判断处理
    for (NSObject *obj in metadataObjects) {
        
        if ([obj isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
            
            // corners, 是二维码的四个角, 但是坐标如果想要使用, 需要进行转换
            // stringValue: 就是二维码对应的数据信息
            AVMetadataMachineReadableCodeObject *resultObj = (AVMetadataMachineReadableCodeObject *)obj;
            
            [resultStrs addObject:resultObj.stringValue];
            
            if (self.isDrawFlag) {
                [self drawQRCodeFrameWithObj:resultObj];
            }
        }
    }
    
    if (self.scanResultBlock) {
        self.scanResultBlock(resultStrs);
    }
}

#pragma mark - 懒加载

- (AVCaptureSession *)session {
    if (_session == nil) {
        // 1.创建一个会话,链接输入和输出
        _session = [[AVCaptureSession alloc] init];
    }
    return _session;
}

- (AVCaptureDeviceInput *)input {
    
    if (_input == nil) {
        
        // 1.获取摄像头设备,并且作为输入设备
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
       
        NSError *error;
        _input = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
        
        if (error != nil) {
            NSLog(@"%@", error);
            return nil;
        }
    }
    
    return _input;
}

- (AVCaptureMetadataOutput *)output {
    
    if (_output == nil) {
        
        // 1.设置输出处理
        // 元数据处理对象: 元数据,就是一种中间数据
        _output = [[AVCaptureMetadataOutput alloc] init];
        // 2.设置元数据处理dialing,接收处理结果
        [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
        
    }
    return _output;
}

- (AVCaptureVideoPreviewLayer *)preLayer {
    
    if (_preLayer == nil) {
        _preLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
    }
    
    return _preLayer;
}
  • 扫描二维码真机运行效果
    • 扫描成功,如果是http://的URL自动调整到该URL页面.
扫描二维码.gif

5.封装二维码工具类

QRCodeTool工具类使用单例,单例代码就不贴了.

工具类的接口头文件QRCodeTool.h

// QRCodeTool.h

#import <UIKit/UIKit.h>
#import "Singleton.h"

@interface QRCodeTool : NSObject

// 单例宏
SingleH(QRCodeTool)


/** 是否描绘二维码边框 */
@property (nonatomic, assign) BOOL isDrawFlag;


/**
 *  根据外界传递过来的内容, 生成一个二维码图片, 并且, 可以根据参数, 添加小头像,在生成后的二维码中间
 *
 *  @param content        二维码内容
 *  @param bigImageSize   大图片的尺寸
 *  @param smallImage     小图片
 *  @param smallImageSize 小图片的尺寸
 *
 *  @return 合成后的二维码图片
 */
+ (UIImage *)imageQRCodeWithContent:(NSString *)content bigImageSize:(CGFloat)bigImageSize smallImage:(UIImage *)smallImage smallImageSize:(CGFloat)smallImageSize;

/**
 *  识别一个图片中所有的二维码, 获取二维码内容
 *
 *  @param sourceImage       需要识别的图片
 *  @param isDrawWRCodeFrame 是否绘制识别到的边框
 *  @param completeBlock     (识别出来的结果数组, 识别出来的绘制二维码图片)
 */
+ (void)detectorQRCodeImageWithSourceImage:(UIImage *)sourceImage isDrawWRCodeFrame:(BOOL)isDrawWRCodeFrame completeBlock:(void(^)(NSArray *resultArray, UIImage *resultImage))completeBlock;

/**
 *  启动二维码扫描
 *
 *  @param inView      显示视频预览的view
 *  @param resultBlock 扫描结果回调
 */
- (void)startScanQRCodeInView:(UIView *)inView resultBlock:(void(^)(NSArray<NSString *> *strArray))resultBlock;

/**
 *  设置扫描识别的区域
 *
 *  @param sourceFrame 扫描区域
 */
- (void)setInterstRect:(CGRect)sourceFrame;

@end

工具类的功能实现QRCodeTool.m

// QRCodeTool.m

#import "QRCodeTool.h"
#import <AVFoundation/AVFoundation.h>

@interface QRCodeTool () <AVCaptureMetadataOutputObjectsDelegate>

/** AVCaptureSession会话 */
@property (nonatomic, strong) AVCaptureSession *session;
/** 输入设备: 摄像头 */
@property (nonatomic, strong) AVCaptureDeviceInput *input;
/** 输出处理对象 */
@property (nonatomic, strong) AVCaptureMetadataOutput *output;
/** 预览图层 */
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *preLayer;
/** 扫描到的二维码回调block,传递结果给外部 */
@property (nonatomic, copy) void(^scanResultBlock)(NSArray<NSString *> *strArray);

@end

@implementation QRCodeTool

// 单例宏
SingleM(QRCodeTool)


#pragma mark - 生成二维码图片
/**
 *  根据外界传递过来的内容, 生成一个二维码图片, 并且, 可以根据参数, 添加小头像,在生成后的二维码中间
 *
 *  @param content        二维码内容
 *  @param bigImageSize   大图片的尺寸
 *  @param smallImage     小图片
 *  @param smallImageSize 小图片的尺寸
 *
 *  @return 合成后的二维码图片
 */
+ (UIImage *)imageQRCodeWithContent:(NSString *)content bigImageSize:(CGFloat)bigImageSize smallImage:(UIImage *)smallImage smallImageSize:(CGFloat)smallImageSize
{
    // 1.将要生成的内容转码为UTF8编码
    NSData *strData = [content dataUsingEncoding:NSUTF8StringEncoding];
    
    // 1.创建一个二维码滤镜
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    
    // 1.1 恢复滤镜默认设置
    [filter setDefaults];
    
    // 2.设置滤镜的输入内容
    // 如果要给滤镜设置输入数据,只能使用KVC设置. key: inputMessage
    // 输入的数据只能传递NSData
    [filter setValue:strData forKey:@"inputMessage"];
    
    // 2.1 设置二维码的纠错率 key: inputCorrectionLevel
    // 纠错率等级: L, M, Q, H
    [filter setValue:@"H" forKey:@"inputCorrectionLevel"];
    
    // 3.直接从二维码滤镜中获取需要的二维码图片
    CIImage *image = [filter outputImage];
    
    // 3.1 默认生成的二维码尺寸为 23x23 ,需要借助位图来处理方法图片, 获取一个高清的图片
    UIImage *newImage = [self createNonInterpolatedUIImageFormCIImage:image withSize:bigImageSize];
    
    // 3.2 判断是否有小图标,如果有小图标,合成小图标
    if (smallImage != nil) {
        newImage = [self createImageBigImage:newImage smallImage:smallImage sizeWH:smallImageSize];
    }
    
    return newImage;
}

/**
 *  根据两个图片,合成一个大图片
 *
 *  @param bigImage   大图的背景图片
 *  @param smallImage 小图标(居中)
 *  @param sizeWH     小图标的尺寸
 *
 *  @return 合成后的图片
 */
+ (UIImage *)createImageBigImage:(UIImage *)bigImage smallImage:(UIImage *)smallImage sizeWH:(CGFloat)sizeWH
{
    CGSize size = bigImage.size;
    
    // 1.开启一个图形山下文
    UIGraphicsBeginImageContext(size);
    
    // 2.绘制大图片
    [bigImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
    
    // 3.绘制小图片
    CGFloat x = (size.width - sizeWH) * 0.5;
    CGFloat y = (size.height - sizeWH) *0.5;
    [smallImage drawInRect:CGRectMake(x, y, sizeWH, sizeWH)];
    
    // 4.取出合成图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 5.关闭图形上下文
    UIGraphicsEndImageContext();
    
    return newImage;
}

/**
 *  根据CIImage生成指定大小的UIImage
 *
 *  @param image CIImage
 *  @param size  图片宽度
 */
+ (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat) size
{
    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;
    CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
    CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
    CIContext *context = [CIContext contextWithOptions:nil];
    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);
    return [UIImage imageWithCGImage:scaledImage];
}


#pragma mark - 识别二维码图片

/**
 *  识别一个图片中所有的二维码, 获取二维码内容
 *
 *  @param sourceImage       需要识别的图片
 *  @param isDrawWRCodeFrame 是否绘制识别到的边框
 *  @param completeBlock     (识别出来的结果数组, 识别出来的绘制二维码图片)
 */
+ (void)detectorQRCodeImageWithSourceImage:(UIImage *)sourceImage isDrawWRCodeFrame:(BOOL)isDrawWRCodeFrame completeBlock:(void(^)(NSArray *resultArray, UIImage *resultImage))completeBlock
{
    // 0.创建上下文
    CIContext *context = [[CIContext alloc] init];
    // 1.创建一个探测器
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}];
    
    // 2.直接开始识别图片,获取图片特征
    CIImage *imageCI = [[CIImage alloc] initWithImage:sourceImage];
    NSArray<CIFeature *> *features = [detector featuresInImage:imageCI];
    
    // 3.读取特征
    UIImage *tempImage = sourceImage;
    NSMutableArray *resultArray = [NSMutableArray array];
    for (CIFeature *feature in features) {
        
        CIQRCodeFeature *tempFeature = (CIQRCodeFeature *)feature;
        
        [resultArray addObject:tempFeature.messageString];
        
        if (isDrawWRCodeFrame) {
            tempImage = [self drawQRCodeFrameFeature:tempFeature toImage:tempImage];
        }
    }
    
    // 4.使用block传递数据给外界
    completeBlock(resultArray, tempImage);
    
}

/**
 *  根据一个特征, 对给定图片, 进行绘制边框
 *
 *  @param feature 特征对象
 *  @param toImage 需要绘制的图片
 *
 *  @return 绘制好边框的图片
 */
+ (UIImage *)drawQRCodeFrameFeature:(CIQRCodeFeature *)feature toImage:(UIImage *)toImage
{
    // bounds,相对于原图片的一个大小
    // 坐标系是以左下角为(0, 0)
    CGRect bounds = feature.bounds;
    
    CGSize size = toImage.size;
    // 1.开启图形上下文
    UIGraphicsBeginImageContext(size);
    
    // 2.绘制图片
    [toImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
    
    // 3.反转上下文坐标系
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextScaleCTM(context, 1, -1);
    CGContextTranslateCTM(context, 0, -size.height);
    
    // 4.绘制边框
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:bounds];
    path.lineWidth = 12;
    [[UIColor redColor] setStroke];
    [path stroke];
    
    // 4.取出图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 5.关闭上下文
    UIGraphicsEndImageContext();
    
    return newImage;
}

#pragma mark - 扫描二维码

/**
 *  启动二维码扫描
 *
 *  @param inView      显示视频预览的view
 *  @param resultBlock 扫描结果回调
 */
- (void)startScanQRCodeInView:(UIView *)inView resultBlock:(void(^)(NSArray<NSString *> *strArray))resultBlock
{
    // 1.记录block,在核实的地方执行
    self.scanResultBlock = resultBlock;
    
    // 2.在天津之前,先判断当前会话是否可以添加
    if ([self.session canAddInput:self.input] &&[self.session canAddOutput:self.output]) {
        
        [self.session addInput:self.input];
        [self.session addOutput:self.output];
        
        // 设置元数据处理的结果类型
        // 如果只需要处理二维码, 那么只需要把处理类型改为二维码类型就可以
        // output.availableMetadataObjectTypes
        // 这个设置, 一定要在会话添加输出处理之后, 才能设置, 否则, 扫描不到, 无法处理
        self.output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];
    }
    
    // 3.1 添加一个视频预览图层
    self.preLayer.frame = inView.bounds;
    
    NSArray *subLayers = inView.layer.sublayers;
    if (subLayers.count == 0) {
        [inView.layer insertSublayer:self.preLayer atIndex:0];
        return;
    }
    
    if (![subLayers containsObject:self.preLayer]) {
        [inView.layer insertSublayer:self.preLayer atIndex:0];
    }
    
    // 4.开始扫描(启动会话)
    [self.session startRunning];
    
}

/**
 *  设置扫描识别的区域
 *
 *  @param sourceFrame 扫描区域
 */
- (void)setInterstRect:(CGRect)sourceFrame
{
    
    // 3.2 设置扫描识别的区域
    // 坐标系: 0, 0 是右上角
    // 横屏状态下的坐标
    CGRect bounds = [UIScreen mainScreen].bounds;
    CGFloat x = sourceFrame.origin.x / bounds.size.width;
    CGFloat y = sourceFrame.origin.y / bounds.size.height;
    CGFloat w = sourceFrame.size.width / bounds.size.width;
    CGFloat h = sourceFrame.size.height / bounds.size.height;
    
    self.output.rectOfInterest = CGRectMake(y, x, h, w);
}

// 移除二维码边框
- (void)removeQRCodeFrame
{
    NSArray *subLayers = self.preLayer.sublayers;
    if (subLayers.count == 0) {
        return;
    }
    
    for (CALayer* layer in subLayers) {
        
        if ([layer isKindOfClass:[CAShapeLayer class]]) {
            [layer removeFromSuperlayer];
        }
    }
}

// 绘制二维码边框
- (void)drawQRCodeFrameWithObj:(AVMetadataMachineReadableCodeObject *)obj
{
    
    //        corners, 是二维码的四个角, 但是坐标如果想要使用, 需要进行转换
    //         print(obj.corners)
    
    // 1. 必须使用视频预览图层, 对坐标进行转换
    AVMetadataMachineReadableCodeObject *resultObj = [self.preLayer transformedMetadataObjectForMetadataObject:obj];
    
    // 根据四个点,绘制一个曲线
    CAShapeLayer *layer = [[CAShapeLayer alloc] init];
    layer.strokeColor = [UIColor redColor].CGColor;
    layer.fillColor = [UIColor clearColor].CGColor;
    layer.lineWidth = 6;
    
    UIBezierPath *path = [[UIBezierPath alloc] init];
    
    // 对四个点的数据进行遍历, 并且根据里面的字典, 创建对应的point
    NSInteger pointCount = resultObj.corners.count;
    for (int i = 0; i < pointCount; i++) {
        
        CFDictionaryRef pointDict = (__bridge CFDictionaryRef)resultObj.corners[i];
        
        CGPoint point = CGPointZero;
        CGPointMakeWithDictionaryRepresentation(pointDict, &point);
        
        // 绘制贝塞尔曲线(二维码边框)
        if (i == 0) {
            [path moveToPoint:point];
        } else {
            [path addLineToPoint:point];
        }
        
    }
    
    [path closePath];
    
    layer.path = path.CGPath;
    
    // 添加形状图层到需要展示的图层上面
    [self.preLayer addSublayer:layer];
}

#pragma mark - AVCaptureMetadataOutputObjectsDelegate代理

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    // 移除之前的边框
    [self removeQRCodeFrame];
    
    NSMutableArray *resultStrs = [NSMutableArray array];
    // 以后, 如果扫描到其他码制, 也会调用这个方法, 所以, 在这里, 如果我们只是处理二维码, 需要对元数据数据类型, 进行判断处理
    for (NSObject *obj in metadataObjects) {
        
        if ([obj isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
            
            // corners, 是二维码的四个角, 但是坐标如果想要使用, 需要进行转换
            // stringValue: 就是二维码对应的数据信息
            AVMetadataMachineReadableCodeObject *resultObj = (AVMetadataMachineReadableCodeObject *)obj;
            
            [resultStrs addObject:resultObj.stringValue];
            
            if (self.isDrawFlag) {
                [self drawQRCodeFrameWithObj:resultObj];
            }
        }
    }
    
    if (self.scanResultBlock) {
        self.scanResultBlock(resultStrs);
    }
}

#pragma mark - 懒加载

- (AVCaptureSession *)session {
    if (_session == nil) {
        // 1.创建一个会话,链接输入和输出
        _session = [[AVCaptureSession alloc] init];
    }
    return _session;
}

- (AVCaptureDeviceInput *)input {
    
    if (_input == nil) {
        
        // 1.获取摄像头设备,并且作为输入设备
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
       
        NSError *error;
        _input = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
        
        if (error != nil) {
            NSLog(@"%@", error);
            return nil;
        }
    }
    
    return _input;
}

- (AVCaptureMetadataOutput *)output {
    
    if (_output == nil) {
        
        // 1.设置输出处理
        // 元数据处理对象: 元数据,就是一种中间数据
        _output = [[AVCaptureMetadataOutput alloc] init];
        // 2.设置元数据处理dialing,接收处理结果
        [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
        
    }
    return _output;
}

- (AVCaptureVideoPreviewLayer *)preLayer {
    
    if (_preLayer == nil) {
        _preLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
    }
    
    return _preLayer;
}

@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,619评论 4 59
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,571评论 25 707
  • 搞定难洗污渍,再不怕穿白衬衫。 要说人生中最尴尬的事,就是临时有约会,胸前却顶着刚刚溅上的咖啡渍。 最操心的事,当...
    纳谷nakko阅读 3,307评论 9 102
  • 远方的星空 铸就着我这渺小的梦 我从没想过去获得 那份美好的爱护 我是平凡人 但我却爱着星空的神秘 是她让我认为自...
    周豪阅读 619评论 35 42
  • 莽莽高原,叠山重谷深处,一湖,永远那么静美,永远那么高贵地不入俗尘,养育着她身边的儿女们,几千年不变。而今,仿佛她...
    Hi黎明阅读 963评论 1 5