@(〓〓 iOS-實用技術)[二維碼的使用]
- 作者: Liwx
- 郵箱: 1032282633@qq.com
目錄
- 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)效果圖
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;
}
- 識別二維碼圖片運行效果圖(注意: 識別二維碼完成后,生成的二維碼有帶紅色邊框)
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頁面.
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