07.二維碼圖片生成,識別二維碼圖片,掃描二維碼

@(〓〓 iOS-實用技術)[二維碼的使用]


目錄

  • 07.二維碼圖片生成,識別二維碼圖片,掃描二維碼
  • 1.二維碼簡介
    • 二維碼使用場景
  • 2.生成二維碼圖片
    • 生成二維碼的基本步驟
    • 生成二維碼的實現(xiàn)參考代碼
  • 3.識別二維碼圖片
    • 識別二維碼基本步驟
    • 識別二維碼圖片參考代碼
  • 4.掃描二維碼
    • 掃描二維碼基本步驟
    • 掃描二維碼核心參考代碼
  • 5.封裝二維碼工具類
    • 工具類的接口頭文件QRCodeTool.h
    • 工具類的功能實現(xiàn)QRCodeTool.m

1.二維碼簡介

從iOS7開始集成了二維碼的生成和讀取功能,> 此前被廣泛使用的zbarsdk目前不支持64位處理器,所以從2015年2月1號起, 不允許不支持64位處理器的APP上架.

二維碼使用場景

  • 1.二維碼使用場景
    • 信息獲染健(名片坷牛、WIFI密碼蚪燕、資料)
    • 手機電商(用戶掃碼、手機直接購物下單)
    • 加好友(QQ, 微信, 掃一掃加好友)
    • 手機支付(掃描商品二維碼以清,通過銀行或第三方支付提供的手機端通道完成支付)

2.生成二維碼圖片

生成二維碼的基本步驟

  • 1.實例化二維碼濾鏡

  • 2.恢復濾鏡的默認屬性

  • 3.將字符串轉(zhuǎn)換成NSData

  • 4.通過KVC設置濾鏡inputMessage數(shù)據(jù)

    • 通過KVC設置濾鏡的 inputCorrectionLevel (容錯率)
  • 5.獲得濾鏡輸出的圖像

  • 6.將CIImage轉(zhuǎn)換成UIImage,并放大顯示

  • 7.通過位圖創(chuàng)建高清圖片

/**
 *  根據(jù)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.創(chuàng)建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];
}

生成二維碼的實現(xiàn)參考代碼


#pragma mark - 生成二維碼圖片
/**
 *  根據(jù)外界傳遞過來的內(nèi)容, 生成一個二維碼圖片, 并且, 可以根據(jù)參數(shù), 添加小頭像,在生成后的二維碼中間
 *
 *  @param content        二維碼內(nèi)容
 *  @param bigImageSize   大圖片的尺寸
 *  @param smallImage     小圖片
 *  @param smallImageSize 小圖片的尺寸
 *
 *  @return 合成后的二維碼圖片
 */
+ (UIImage *)imageQRCodeWithContent:(NSString *)content bigImageSize:(CGFloat)bigImageSize smallImage:(UIImage *)smallImage smallImageSize:(CGFloat)smallImageSize
{
    // 1.將要生成的內(nèi)容轉(zhuǎn)碼為UTF8編碼
    NSData *strData = [content dataUsingEncoding:NSUTF8StringEncoding];
    
    // 1.創(chuàng)建一個二維碼濾鏡
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    
    // 1.1 恢復濾鏡默認設置
    [filter setDefaults];
    
    // 2.設置濾鏡的輸入內(nèi)容
    // 如果要給濾鏡設置輸入數(shù)據(jù),只能使用KVC設置. key: inputMessage
    // 輸入的數(shù)據(jù)只能傳遞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;
}

/**
 *  根據(jù)兩個圖片,合成一個大圖片
 *
 *  @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;
}

/**
 *  根據(jù)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.創(chuàng)建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];
}
  • 生成二維碼實現(xiàn)效果圖
01.生成二維碼實現(xiàn)效果圖.gif

3.識別二維碼圖片

識別二維碼基本步驟

  • 1.創(chuàng)建一個上下文

  • 2.創(chuàng)建一個探測器

  • 3.轉(zhuǎn)換原圖片為 CIImage

  • 4.獲取探測器識別的圖像特征

  • 5.遍歷圖片特征, 獲取數(shù)據(jù)

  • 6.繪制識別到的二維碼邊框

  • 7.傳遞識別的數(shù)據(jù)給外界.


識別二維碼圖片參考代碼

#pragma mark - 識別二維碼圖片

/**
 *  識別一個圖片中所有的二維碼, 獲取二維碼內(nèi)容
 *
 *  @param sourceImage       需要識別的圖片
 *  @param isDrawWRCodeFrame 是否繪制識別到的邊框
 *  @param completeBlock     (識別出來的結果數(shù)組, 識別出來的繪制二維碼圖片)
 */
+ (void)detectorQRCodeImageWithSourceImage:(UIImage *)sourceImage isDrawWRCodeFrame:(BOOL)isDrawWRCodeFrame completeBlock:(void(^)(NSArray *resultArray, UIImage *resultImage))completeBlock
{
    // 0.創(chuàng)建上下文
    CIContext *context = [[CIContext alloc] init];
    // 1.創(chuàng)建一個探測器
    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傳遞數(shù)據(jù)給外界
    completeBlock(resultArray, tempImage);
    
}

/**
 *  根據(jù)一個特征, 對給定圖片, 進行繪制邊框
 *
 *  @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.反轉(zhuǎn)上下文坐標系
    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.設置元數(shù)據(jù)輸出處理對象

    • 3.1 實例化拍攝元數(shù)據(jù)輸出
    • 3.2 設置輸出數(shù)據(jù)代理
  • 4.添加拍攝會話

  • 5.視頻預覽圖層(不是必須)

  • 6.啟動會話

  • 7.監(jiān)聽元數(shù)據(jù)處理后的結果

掃描二維碼核心參考代碼


#pragma mark - 掃描二維碼

/**
 *  啟動二維碼掃描
 *
 *  @param inView      顯示視頻預覽的view
 *  @param resultBlock 掃描結果回調(diào)
 */
- (void)startScanQRCodeInView:(UIView *)inView resultBlock:(void(^)(NSArray<NSString *> *strArray))resultBlock
{
    // 1.記錄block,在核實的地方執(zhí)行
    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];
        
        // 設置元數(shù)據(jù)處理的結果類型
        // 如果只需要處理二維碼, 那么只需要把處理類型改為二維碼類型就可以
        // 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];
    
}

/**
 *  設置掃描識別的區(qū)域
 *
 *  @param sourceFrame 掃描區(qū)域
 */
- (void)setInterstRect:(CGRect)sourceFrame
{
    
    // 3.2 設置掃描識別的區(qū)域
    // 坐標系: 0, 0 是右上角
    // 橫屏狀態(tài)下的坐標
    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, 是二維碼的四個角, 但是坐標如果想要使用, 需要進行轉(zhuǎn)換
    //         print(obj.corners)
    
    // 1. 必須使用視頻預覽圖層, 對坐標進行轉(zhuǎn)換
    AVMetadataMachineReadableCodeObject *resultObj = [self.preLayer transformedMetadataObjectForMetadataObject:obj];
    
    // 根據(jù)四個點,繪制一個曲線
    CAShapeLayer *layer = [[CAShapeLayer alloc] init];
    layer.strokeColor = [UIColor redColor].CGColor;
    layer.fillColor = [UIColor clearColor].CGColor;
    layer.lineWidth = 6;
    
    UIBezierPath *path = [[UIBezierPath alloc] init];
    
    // 對四個點的數(shù)據(jù)進行遍歷, 并且根據(jù)里面的字典, 創(chuàng)建對應的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];
    // 以后, 如果掃描到其他碼制, 也會調(diào)用這個方法, 所以, 在這里, 如果我們只是處理二維碼, 需要對元數(shù)據(jù)數(shù)據(jù)類型, 進行判斷處理
    for (NSObject *obj in metadataObjects) {
        
        if ([obj isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
            
            // corners, 是二維碼的四個角, 但是坐標如果想要使用, 需要進行轉(zhuǎn)換
            // stringValue: 就是二維碼對應的數(shù)據(jù)信息
            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.創(chuàng)建一個會話,鏈接輸入和輸出
        _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.設置輸出處理
        // 元數(shù)據(jù)處理對象: 元數(shù)據(jù),就是一種中間數(shù)據(jù)
        _output = [[AVCaptureMetadataOutput alloc] init];
        // 2.設置元數(shù)據(jù)處理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自動調(diào)整到該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;


/**
 *  根據(jù)外界傳遞過來的內(nèi)容, 生成一個二維碼圖片, 并且, 可以根據(jù)參數(shù), 添加小頭像,在生成后的二維碼中間
 *
 *  @param content        二維碼內(nèi)容
 *  @param bigImageSize   大圖片的尺寸
 *  @param smallImage     小圖片
 *  @param smallImageSize 小圖片的尺寸
 *
 *  @return 合成后的二維碼圖片
 */
+ (UIImage *)imageQRCodeWithContent:(NSString *)content bigImageSize:(CGFloat)bigImageSize smallImage:(UIImage *)smallImage smallImageSize:(CGFloat)smallImageSize;

/**
 *  識別一個圖片中所有的二維碼, 獲取二維碼內(nèi)容
 *
 *  @param sourceImage       需要識別的圖片
 *  @param isDrawWRCodeFrame 是否繪制識別到的邊框
 *  @param completeBlock     (識別出來的結果數(shù)組, 識別出來的繪制二維碼圖片)
 */
+ (void)detectorQRCodeImageWithSourceImage:(UIImage *)sourceImage isDrawWRCodeFrame:(BOOL)isDrawWRCodeFrame completeBlock:(void(^)(NSArray *resultArray, UIImage *resultImage))completeBlock;

/**
 *  啟動二維碼掃描
 *
 *  @param inView      顯示視頻預覽的view
 *  @param resultBlock 掃描結果回調(diào)
 */
- (void)startScanQRCodeInView:(UIView *)inView resultBlock:(void(^)(NSArray<NSString *> *strArray))resultBlock;

/**
 *  設置掃描識別的區(qū)域
 *
 *  @param sourceFrame 掃描區(qū)域
 */
- (void)setInterstRect:(CGRect)sourceFrame;

@end

工具類的功能實現(xiàn)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;
/** 掃描到的二維碼回調(diào)block,傳遞結果給外部 */
@property (nonatomic, copy) void(^scanResultBlock)(NSArray<NSString *> *strArray);

@end

@implementation QRCodeTool

// 單例宏
SingleM(QRCodeTool)


#pragma mark - 生成二維碼圖片
/**
 *  根據(jù)外界傳遞過來的內(nèi)容, 生成一個二維碼圖片, 并且, 可以根據(jù)參數(shù), 添加小頭像,在生成后的二維碼中間
 *
 *  @param content        二維碼內(nèi)容
 *  @param bigImageSize   大圖片的尺寸
 *  @param smallImage     小圖片
 *  @param smallImageSize 小圖片的尺寸
 *
 *  @return 合成后的二維碼圖片
 */
+ (UIImage *)imageQRCodeWithContent:(NSString *)content bigImageSize:(CGFloat)bigImageSize smallImage:(UIImage *)smallImage smallImageSize:(CGFloat)smallImageSize
{
    // 1.將要生成的內(nèi)容轉(zhuǎn)碼為UTF8編碼
    NSData *strData = [content dataUsingEncoding:NSUTF8StringEncoding];
    
    // 1.創(chuàng)建一個二維碼濾鏡
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    
    // 1.1 恢復濾鏡默認設置
    [filter setDefaults];
    
    // 2.設置濾鏡的輸入內(nèi)容
    // 如果要給濾鏡設置輸入數(shù)據(jù),只能使用KVC設置. key: inputMessage
    // 輸入的數(shù)據(jù)只能傳遞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;
}

/**
 *  根據(jù)兩個圖片,合成一個大圖片
 *
 *  @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;
}

/**
 *  根據(jù)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.創(chuàng)建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 - 識別二維碼圖片

/**
 *  識別一個圖片中所有的二維碼, 獲取二維碼內(nèi)容
 *
 *  @param sourceImage       需要識別的圖片
 *  @param isDrawWRCodeFrame 是否繪制識別到的邊框
 *  @param completeBlock     (識別出來的結果數(shù)組, 識別出來的繪制二維碼圖片)
 */
+ (void)detectorQRCodeImageWithSourceImage:(UIImage *)sourceImage isDrawWRCodeFrame:(BOOL)isDrawWRCodeFrame completeBlock:(void(^)(NSArray *resultArray, UIImage *resultImage))completeBlock
{
    // 0.創(chuàng)建上下文
    CIContext *context = [[CIContext alloc] init];
    // 1.創(chuàng)建一個探測器
    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傳遞數(shù)據(jù)給外界
    completeBlock(resultArray, tempImage);
    
}

/**
 *  根據(jù)一個特征, 對給定圖片, 進行繪制邊框
 *
 *  @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.反轉(zhuǎn)上下文坐標系
    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 掃描結果回調(diào)
 */
- (void)startScanQRCodeInView:(UIView *)inView resultBlock:(void(^)(NSArray<NSString *> *strArray))resultBlock
{
    // 1.記錄block,在核實的地方執(zhí)行
    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];
        
        // 設置元數(shù)據(jù)處理的結果類型
        // 如果只需要處理二維碼, 那么只需要把處理類型改為二維碼類型就可以
        // 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];
    
}

/**
 *  設置掃描識別的區(qū)域
 *
 *  @param sourceFrame 掃描區(qū)域
 */
- (void)setInterstRect:(CGRect)sourceFrame
{
    
    // 3.2 設置掃描識別的區(qū)域
    // 坐標系: 0, 0 是右上角
    // 橫屏狀態(tài)下的坐標
    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, 是二維碼的四個角, 但是坐標如果想要使用, 需要進行轉(zhuǎn)換
    //         print(obj.corners)
    
    // 1. 必須使用視頻預覽圖層, 對坐標進行轉(zhuǎn)換
    AVMetadataMachineReadableCodeObject *resultObj = [self.preLayer transformedMetadataObjectForMetadataObject:obj];
    
    // 根據(jù)四個點,繪制一個曲線
    CAShapeLayer *layer = [[CAShapeLayer alloc] init];
    layer.strokeColor = [UIColor redColor].CGColor;
    layer.fillColor = [UIColor clearColor].CGColor;
    layer.lineWidth = 6;
    
    UIBezierPath *path = [[UIBezierPath alloc] init];
    
    // 對四個點的數(shù)據(jù)進行遍歷, 并且根據(jù)里面的字典, 創(chuàng)建對應的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];
    // 以后, 如果掃描到其他碼制, 也會調(diào)用這個方法, 所以, 在這里, 如果我們只是處理二維碼, 需要對元數(shù)據(jù)數(shù)據(jù)類型, 進行判斷處理
    for (NSObject *obj in metadataObjects) {
        
        if ([obj isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
            
            // corners, 是二維碼的四個角, 但是坐標如果想要使用, 需要進行轉(zhuǎn)換
            // stringValue: 就是二維碼對應的數(shù)據(jù)信息
            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.創(chuàng)建一個會話,鏈接輸入和輸出
        _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.設置輸出處理
        // 元數(shù)據(jù)處理對象: 元數(shù)據(jù),就是一種中間數(shù)據(jù)
        _output = [[AVCaptureMetadataOutput alloc] init];
        // 2.設置元數(shù)據(jù)處理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
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惹苗,一起剝皮案震驚了整個濱河市锨用,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泽裳,老刑警劉巖瞒斩,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涮总,居然都是意外死亡胸囱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門瀑梗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烹笔,“玉大人,你說我怎么就攤上這事抛丽“埃” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵亿鲜,是天一觀的道長允蜈。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么饶套? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任漩蟆,我火速辦了婚禮,結果婚禮上妓蛮,老公的妹妹穿的比我還像新娘爆安。我一直安慰自己,他們只是感情好仔引,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布扔仓。 她就那樣靜靜地躺著,像睡著了一般咖耘。 火紅的嫁衣襯著肌膚如雪翘簇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天儿倒,我揣著相機與錄音版保,去河邊找鬼。 笑死夫否,一個胖子當著我的面吹牛彻犁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凰慈,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼汞幢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了微谓?” 一聲冷哼從身側(cè)響起森篷,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎豺型,沒想到半個月后仲智,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡姻氨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年钓辆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肴焊。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡前联,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抖韩,到底是詐尸還是另有隱情蛀恩,我是刑警寧澤疫铜,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布茂浮,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏席揽。R本人自食惡果不足惜顽馋,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幌羞。 院中可真熱鬧寸谜,春花似錦、人聲如沸属桦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聂宾。三九已至果善,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間系谐,已是汗流浹背巾陕。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纪他,地道東北人鄙煤。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像茶袒,于是被迫代替她去往敵國和親梯刚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫薪寓、插件乾巧、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,024評論 4 62
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,504評論 25 707
  • 搞定難洗污漬,再不怕穿白襯衫预愤。 要說人生中最尷尬的事沟于,就是臨時有約會,胸前卻頂著剛剛濺上的咖啡漬植康。 最操心的事贡必,當...
    納谷nakko閱讀 3,352評論 9 102
  • 遠方的星空 鑄就著我這渺小的夢 我從沒想過去獲得 那份美好的愛護 我是平凡人 但我卻愛著星空的神秘 是她讓我認為自...
    周豪閱讀 638評論 35 42
  • 莽莽高原,疊山重谷深處史辙,一湖诵姜,永遠那么靜美,永遠那么高貴地不入俗塵冻记,養(yǎng)育著她身邊的兒女們睡毒,幾千年不變。而今冗栗,仿佛她...
    Hi黎明閱讀 1,051評論 1 5