二维码技术
QRCode:quick Response Code
作用:将数据转化为图形,本质内容就是字符串.
ZXing和ZBar第三方框架.
官方框架:
二维码生成:CoreImage
二维码扫描:AVFoundation
目标:生成一张二维码图片(字符串->二维码)
目标:给二维码修改黑白颜色(主要色系差别要对应,不然无法扫描)
加个滤镜,CIFalseColor
导入CoreImage框架:提供了针对静态或视频的处理分析的相关功能.
重点:过滤器,滤镜(核心类:CIFiter)
作用:将输入的图片进行处理,生成新的图片.二维码滤镜不用输入图片,通过字符串输入就可以了.获取到可用到的滤镜名
[CIFilter filterNames...]
拿到这个kCICategoryBuiltIn键,指的是CoreIamge创建并返回指定名字的滤镜
所内键的滤镜(已经提供好的)
看文档找滤镜名的意思(比较多)
找到QRCode滤镜(二维码)-
为滤镜配置参数
- 注意参数的配置是使用KVC来设置的,不是通过属性设置的.
- inputMessage(NSData)
- input修正程度(修改识别程度可以更高)
- 注意参数的配置是使用KVC来设置的,不是通过属性设置的.
获取滤镜生成的图片
outputImage属性.(类型CIImage)变为UIImage变得模糊了
图片大小
CIImage的属性content(查一下)才有size属性.
怎么对CIImage进行放大?(矢量方法,无损的)
二维码识别:只是识别三个方框内容.其他部分可以自动识别,只要不被挡住太多.
- (void)setupQRCode {
// 1.获取到指定类中所有可能用到的滤镜名
//kCICategoryBuiltIn 指的是 CoreImage 所内建的滤镜 (已经提供好的)
NSArray *filters = [CIFilter filterNamesInCategory:kCICategoryBuiltIn];
NSLog(@"%@",filters);
// 2. 创建滤镜
self.filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
// 3. 为滤镜配置参数
//inputMessage,inputCorrectionLevel
//字符串转数据
NSData *data = [@"集美貌与才华于一身的女子---李欣" dataUsingEncoding:NSUTF8StringEncoding];
// 设置二维码内容
[self.filter setValue:data forKey:@"inputMessage"];
// 二维码修正度
[self.filter setValue:@"H" forKey:@"inputCorrectionLevel"];
// 获取滤镜处理图片
CIImage *outputImage = self.filter.outputImage;
// 处理滤镜图片的无损放大
outputImage = [outputImage imageByApplyingTransform:CGAffineTransformMakeScale(10, 10)];
// 增加特殊效果
// NSDictionary *para = @{
// @"inputImage" : outputImage,
// @"inputColor0" : [CIColor colorWithRed:1.0 green:1.0 blue:1.0],
// @"inputColor1" :[CIColor colorWithRed:0.3 green:0.8 blue:0.8 alpha:0.8] ,
// };
//
// CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor" withInputParameters:para];
//
// outputImage = colorFilter.outputImage;
//转化为UIimage
UIImage *resultImage = [UIImage imageWithCIImage:outputImage];
// 绘图
resultImage = [resultImage imageWithUseIcon:[UIImage imageNamed:@"C4EA925D-9ED2-47CB-A49C-C1BA9A33B8F3"]];
self.imageView.image = resultImage;
}
目标:在二维码图片中间添加图片,绘制成新的图片
图形上下文技术
- 开启图形上下文
- 画原图
- 画中间头像
- 从上下文中获取最终图片
- 关闭图形上下文.
UIImage分类中的方法:
- (UIImage *)imageWithUseIcon:(UIImage *)iconImage {
// 绘图
//1. 开启图形上下文
UIGraphicsBeginImageContext(self.size);
//2. 换原图
[self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
//3. 画中间的图
CGFloat wh = MIN(self.size.width, self.size.height) * 0.25;
CGFloat x = (self.size.width/2 - wh/2);
CGFloat y = (self.size.height - wh)*0.5;
[iconImage drawInRect:CGRectMake(x, y, wh, wh)];
//4.得到新图
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//5. 关闭图形上下文
UIGraphicsEndPDFContext();
return image;
}
目标:二维码的图片识别
相关框架CoreImage.
相关类:CIDetector.(识别特定的功能)
- 创建一个特征检测器CIDetector(参数配置)
- 注意直接通过UIImage加载到的图片,直接获取.CIImage属性,是nil.原因:UIKit内容都是CoreGraphics,所以能转换CGImage,而CIImage表示的是图片的数据与UIImage没有直接的关系,查看文档发现可以转化从CGImage到CIImage.
- 通过URL加载得到
...整理一下
目标:二维码扫描器
原理:
- 摄像头和设备输入端会把摄像头的数据流出去(数据流)
- session会话:可以协调输入/输出
- 数据处理器(判断含不含二维码)
- 展示摄像头数据?为什么是摄像头直接展示session会话),只是个展示作用.
不理解点:哪里开始作为二维码的数据起点和终端(怎么判断的),内部会做出处理.
代码部分:
- 输入端
- 用到AVFoundation框架,导入框架
- AVCaptureDevice(capture捕获)
- AVCaptureInput(绑定输入端)
- 会话端(关联中心点)
- AVCaptureSession(Session关键字会话)
- 配置输入端和输出端
- 添加输入端到会话端
- 添加输出端(后面做的)
- 启动会话(后面做的).
- 创建输出端
- AVCaptureOutput:子类AVCaptureMetdataOutput:表示解析特定数据类型的输出端.
- 配置(查看文档,属性)
2.1. 要解析的类型metdataObjectType,这个字符串类型是限定的.打印一下输出类型.获取二维码类型
2.2. 设置解析结果的数据回调(使用代理)
2.3. 实现代理方法完成回调,返回数据数组.(多个),AVMetadataMachineReadableCodeObject基类类型(一维码,二维码),怎么知道使这个类型,文档,或者直接打印,就知道类型)
2.4. 停止会员(因为已经扫到了一个二维码,需要关闭流)
但是界面还是白的 - 添加输出端到会话端.
- 展示预览图层.(系统已经提供好了)
- AVCaptureVideoPreViewLayer(查文档有相关代码写法)
- 添加灰暗的遮盖,通过路径绘制形状.
补充:输出百分比值(只有一块可以扫),能够限定输出端解析数据的范围
rectOfInterest:
- 是百分比值
- 顺序不是x,y,height,width.和传统的不同.
错误提示,输入端和输出端失败.要真机.还是报错?你的访问摄像头用户隐私配置了么??Xcode8当中使用真机调试,配置了OS_ACTIVITY_MODE为disable时,NSLog无法输出.
封装二维码:
- 点击按钮弹出二维码弹框
- 封装到控制器中.
完善: 用layer来做(继续封装类,只留中心位置的rect)
- CAShapeLayer:用来绘制某些形状的图层
- 形状有路径来决定.(UIBezierPath贝塞尔曲线)
- .CGPath得到形状的路径.
代码部分:
- (void)viewDidLoad {
[super viewDidLoad];
// 输入端(设备绑定输入端)
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (error) {
NSLog(@"创建输出端错误");
return;
}
//会话端
_session = [[AVCaptureSession alloc]init];
[_session addInput:input];
//输出端
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc]init];
// 2. 首先判断输入端能够添加到会话端波
if ([_session canAddOutput:output] == NO) {
NSLog(@"输出端添加失败!");
return;
}
[_session addOutput:output];
// 3.1 设置输出端当前要解析的数据类型
output.metadataObjectTypes = @[@"org.iso.QRCode"];
// Sending 'ZLQRCodeScannerViewController *const __strong' to parameter of incompatible type 'id'
// 3.2 设置解析结果的数据回调, 让回调方法在指定的队列当中执行
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// ========== 3.3 修改输出端的有效数据解析范围 ==========
// 值是CGRect, 默认值是 {0, 0, 1, 1}, 都是百分比值
// 假设在屏幕中间, 固定250 * 250
CGFloat wh = 250;
CGFloat width = wh / self.view.bounds.size.width;
CGFloat height = wh / self.view.bounds.size.height;
CGFloat x = (self.view.bounds.size.width - wh) * 0.5 / self.view.bounds.size.width;
CGFloat y = (self.view.bounds.size.height - wh) * 0.5 / self.view.bounds.size.height;
// 参数是 {y, x, height, width}
output.rectOfInterest = CGRectMake(y, x, height, width);
// ========== 4. 预览图层 ==========
// 预览图层, 能将Capture捕获的数据展示到界面上
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_session];
// 注意: 要配置frame值
previewLayer.frame = self.view.bounds;
// 添加预览图层
[self.view.layer addSublayer:previewLayer];
// ========== 添加遮盖 ==========
// [self addCover];
CGFloat coverX = (self.view.bounds.size.width - wh) * 0.5;
CGFloat coverY = (self.view.bounds.size.height - wh) * 0.5;
[self.view.layer coverButRect:CGRectMake(coverX, coverY, wh, wh)];
// ========== 5. 启动会话 ==========
// 开始运行!
[_session startRunning];
}
/// 使用图层的CAShapeLayer子类,进行绘制,通过路径决定
- (void)addCover
{
// 用来绘制某些形状的图层, 由路径来决定
CAShapeLayer *topLayer = [[CAShapeLayer alloc] init];
// 配置矩形路径
UIBezierPath *rect = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, self.view.bounds.size.width, 200)];
topLayer.path = rect.CGPath;
// 配置填充的颜色和透明度
topLayer.fillColor = [UIColor lightGrayColor].CGColor;
topLayer.opacity = 0.6;
[self.view.layer addSublayer:topLayer];
}
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
/**
输出端捕获到指定的数据类型时触发
*/
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
// metadataObjects: 保存了解析结果的数组
// AVMetadataObject: 表示结果的类型, 是基类
// 是一个具体的结果类型, 用来表示机器可识别编码(一维码, 二维码等等)
// AVMetadataMachineReadableCodeObject
// NSLog(@"%@", metadataObjects);
// printf("%s", [metadataObjects.description cStringUsingEncoding:NSUTF8StringEncoding]);
for (AVMetadataMachineReadableCodeObject *object in metadataObjects) {
printf("%s", [object.stringValue cStringUsingEncoding:NSUTF8StringEncoding]);
}
// 让会话停止
[_session stopRunning];
}
CALayer分类中布局的方法,得到中间方形
- (void)coverButRect:(CGRect)rect
{
// Rect为保留的矩形frame值
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat leftWidth = (screenWidth - rect.size.width) / 2;
CAShapeLayer* layerTop = [[CAShapeLayer alloc] init];
layerTop.fillColor = [UIColor blackColor].CGColor;
layerTop.opacity = 0.5;
layerTop.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, screenWidth, rect.origin.y)].CGPath;
[self addSublayer:layerTop];
CAShapeLayer* layerLeft = [[CAShapeLayer alloc] init];
layerLeft.fillColor = [UIColor blackColor].CGColor;
layerLeft.opacity = 0.5;
layerLeft.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, rect.origin.y, leftWidth, rect.size.height)].CGPath;
[self addSublayer:layerLeft];
CAShapeLayer* layerRight = [[CAShapeLayer alloc] init];
layerRight.fillColor = [UIColor blackColor].CGColor;
layerRight.opacity = 0.5;
layerRight.path = [UIBezierPath bezierPathWithRect:CGRectMake(screenWidth - leftWidth, rect.origin.y, rect.size.width, rect.size.height)].CGPath;
[self addSublayer:layerRight];
CAShapeLayer* layerBottom = [[CAShapeLayer alloc] init];
layerBottom.fillColor = [UIColor blackColor].CGColor;
layerBottom.opacity = 0.5;
layerBottom.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, CGRectGetMaxY(rect), screenWidth, screenHeight - CGRectGetMaxY(rect))].CGPath;
[self addSublayer:layerBottom];
}