生成二维码
生成二维码主要用到 CIFilter,主要的函数如下
CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
[filter setDefaults];
NSData *data = [@"微信 Yasic" dataUsingEncoding:NSUTF8StringEncoding];
[filter setValue:data forKey:@"inputMessage"];
[filter setValue:@"H" forKey:@"inputCorrectionLevel"];
CIImage *outPutImage = [filter outputImage];
但是要注意的是,接下来如果直接用 CIImage 来生成 UIImage,得到的效果可能如下图所示
UIImage *result1 = [UIImage imageWithCIImage:outPutImage];
UncorrectQRImage.png
可以看到图片很模糊,此时网上大部分博客给出的解决方案如下
/// 将 CGRect 取整到最近的完整点是非常重要的。小数值会让边框画在像素边界处。因为像素已经是最小单元(不能再细分),小数值会使绘制时取周围几个像素的平均值,这样看起来就模糊了。CGRectIntegral 将表示原点的值向下取整,表示大小的值向上取整,这样就保证了你的绘制代码平整地对齐到像素边界。作为一个经验性的原则,如果你在执行任何一个可能产生小数值的操作(例如除法,CGGetMid[X|Y],或是 CGRectDivide),在把一矩形作为视图的边框之前应该用CGRectIntegral正则化它。
CGRect extent = CGRectIntegral(image.extent);
CGFloat size = 300;
CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent)); // 计算需要缩放的比例
size_t width = CGRectGetWidth(extent) * scale; // 计算缩放后的尺寸
size_t height = CGRectGetHeight(extent) * scale;
CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
/// CIContext 属于Core Image框架(文档中提到主要的功能就是,用内置或自定义的过滤器处理图片和视频 以及在视频图片中检测面部和眼睛子类的特征和跟踪面部。和 Core Graphics 的主要区别 就是更注重于视频图片的加工处理)的,是一个 OC 对象
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef bitmapImage = [context createCGImage:image fromRect:extent]; // 转化为位图
/// CGContextRef 属于Core Graphics 框架(使用 Quartz 进行 2D 渲染,处理基于路径的绘图、抗锯齿渲染、渐变、图像、颜色管理、pdf文档等 2D 绘图渲染功能)
/// data 是一个指针,指向存储绘制的bitmap context的实际数据的地址,最少大小为bytesPerRow* height.可以传入null,让quartz自动分配计算
/// width/height bitmap的宽度,高度,以像素为单位
/// bitsPerComponent 一个component占据多少字节。对于32bit的RGBA空间,则是8(8*4=32)。
/// bytesPerRow 每一行的byte数目。如果data传入null,这里传入0,则会自动计算
/// space 颜色空间,一般就是DeviceRGB
/// bitmapInfo,一个常量,指定了是否具有alpha通道,alpha通道的位置,像素点存储的数据类型是float还是Integer等信息
CGContextRef bitmapContextRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
CGContextSetInterpolationQuality(bitmapContextRef, kCGInterpolationNone);//不允许上下文在各个保真度等级插入像素
/// 该方法控制坐标系统水平方向上缩放 sx,垂直方向上缩放 sy。在缩放后的坐标系统上绘制图形时,所有点的 X 坐标都相当于乘以 sx 因子,所有点的 Y 坐标都相当于乘以 sy 因子
CGContextScaleCTM(bitmapContextRef, scale, scale); //对位图上下文进行缩放
CGContextDrawImage(bitmapContextRef, extent, bitmapImage);//绘制位图到位图上下文
CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapContextRef);//生成缩放后的位图
UIImage *outputImage = [UIImage imageWithCGImage:scaledImage];
CGContextRelease(bitmapContextRef);
CGImageRelease(bitmapImage);
它的原理是,将 CIImage 绘制到一个灰度图上下文中(CGColorSpaceCreateDeviceGray),根据给定的 size,对画布和图像进行缩放。
这样生成的图片如下所示
CorrectQRImage.png可以看出二维码的确清晰了很多,但是没有博客具体说明了真正影响清晰度的原因,实际上影响清晰度的原因是这一句代码
CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);//允许上下文在各个保真度等级插入像素
这里对图像插值质量做了设置,图像插值的概念如下
插值是对原图像的像素重新分布,从而来改变像素数量的一种方法。在图像放大过程中,像素也相应地增加,增加的过程就是“插值”发生作用的过程,“插值”程序自动选择信息较好的像素作为增加、弥补空白像素的空间,而并非只使用临近的像素,所以在放大图像时,图像看上去会比较平滑、干净。不过需要说明的是插值并不能增加图像信息,尽管图像尺寸变大,但效果也相对要模糊些,过程可以理解为白酒掺水。
而 CGContextSetInterpolationQuality 可能默认采用了高品质的插值函数,导致生成的图片由于进行了冗余的像素插值而变得模糊,因此将插值品质设置为 NONE,禁止进行插值操作,就可以保证像素按照原本的分布来缩放了。
-
Low: Nearest neighbor interpolation
-
Medium: Linear or bilinear interpolation
-
High: Quadratic or bicubic interpolation
-
给二维码加水印
主要原理就是在生成的二维码图片上 drawImage 自己的水印图片
UIGraphicsBeginImageContextWithOptions(outputImage.size, NO, [[UIScreen mainScreen] scale]);
[outputImage drawInRect:CGRectMake(0,0 , size, size)];
UIImage *waterimage = [UIImage imageNamed:@"DogLogo"];
[waterimage drawInRect:CGRectMake((size - waterImagesize)/2.0, (size - waterImagesize)/2.0, waterImagesize, waterImagesize)];
UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
WaterQRImage.png
- 生成彩色二维码
彩色二维码原理是遍历图片的每一个像素点,进行 rgb 值的改变。
void ProviderReleaseData (void *info, const void *data, size_t size){
free((void*)data);
}
{
UIColor *targetColor = [UIColor colorWithRed:0.3 green:0.7 blue:0.7 alpha:1.0];
const CGFloat *components = CGColorGetComponents(targetColor.CGColor);
CGFloat red = components[0] * 255;
CGFloat green = components[1] * 255;
CGFloat blue = components[2] * 255;//分别获取 RGB 值
int imageWidth = outputImage.size.width;
int imageHeight = outputImage.size.height;
size_t bytesPerRow = imageWidth * 4;
uint32_t *rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef rgbContext = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
CGContextDrawImage(rgbContext, CGRectMake(0, 0, imageWidth, imageHeight), outputImage.CGImage); //绘制图像到rgb上下文中
int pixelNum = imageWidth * imageHeight; //像素总数,每一个像素包含rgba四个通道
uint32_t *pCurPtr = rgbImageBuf;
for (int i = 0; i < pixelNum; i++, pCurPtr++){ //遍历像素
if ((*pCurPtr & 0xFFFFFF00) < 0x99999900){ // 按rgb区分
uint8_t *ptr = (uint8_t*)pCurPtr;
ptr[3] = red;
ptr[2] = green;
ptr[1] = blue;
}else{
uint8_t* ptr = (uint8_t*)pCurPtr;
ptr[0] = 255;
}
}
// rgbImageBuf 里包含了 image 的像素信息
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, ProviderReleaseData);
CGImageRef imageRef = CGImageCreate(imageWidth, // 图片的宽度
imageHeight, // 图片的高度
8, // 一个component占据多少字节
32, // 每一个像素占用的bits
bytesPerRow, // 每一行占用多少bytes,宽度乘以 4
colorSpace,
kCGImageAlphaLast | kCGBitmapByteOrder32Little,
dataProvider, // 数据源提供者
NULL, // 一个解码数组
true, // 抗锯齿参数
kCGRenderingIntentDefault); //图片渲染相关参数
outputImage = [UIImage imageWithCGImage:imageRef];
CGDataProviderRelease(dataProvider);
}
ColorfulQRImage.png
相机识别二维码
这里不赘述关于 iOS 相机的配置过程,只关注与二维码相关的步骤。
iOS 有识别二维码的 API,只需要将 AVCaptureMetadataOutput 配置到 session 的输出中,然后设置其 AVCaptureMetadataOutputObjectsDelegate 代理对象,系统检测到二维码后会调用相应的委托方法。
- 配置 AVCaptureMetadataOutput
- (AVCaptureMetadataOutput *)metaDataOutput
{
if (!_metaDataOutput) {
_metaDataOutput = [[AVCaptureMetadataOutput alloc] init];
[_metaDataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
_metaDataOutput.rectOfInterest = CGRectMake(0, 0, 1, 1);
}
return _metaDataOutput;
}
{
// 配置QROutput
if ([self.captureSession canAddOutput:self.metaDataOutput]){
// 顺序相反会崩溃,因为未配置到 session 时 availableMetadataObjectTypes 为空
[self.captureSession addOutput:self.metaDataOutput];
self.metaDataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];
}
}
- 回调方法
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray<AVMetadataMachineReadableCodeObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
if (metadataObjects.count == 0) {
return;
}
NSString *result = [metadataObjects.firstObject stringValue];
NSLog(@"%@", result);
}
这里要注意一点,在设置 metaDatOutput 时设置了一个 rectOfInterest 属性,它能限制扫描二维码的区域,它定义了对相机采集的每一帧图像里代码真正感兴趣的检测区域,它有两个特点
- rectOfInterest 结构体的四个值取值范围在 0~1,即指明对于每一帧,其关心的范围的相对比例
- rectOfInterest 默认是横屏模式,其 x y 值与通常坐标系相反,也就是说它的真正含义是 CGRectMake(y坐标, x坐标, 高度, 宽度)
当然默认情况下 rectOfInterest 的值是 CGRectMake(0, 0, 1, 1),即整个帧范围。
识别内存中二维码图片
对于网络或者相册选取后读到内存中的图片,以及项目中自带的图片,要检测二维码信息就比较简单,直接使用 CIDetector 就可以检测。
CIContext *context = [CIContext contextWithOptions:nil];
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
CIImage *targetImage = [CIImage imageWithCGImage:[UIImage imageNamed:@"CorrectQRImage.png"].CGImage];
NSArray *features = [detector featuresInImage:targetImage];
if (features.count == 0) {
} else {
CIQRCodeFeature *feature = features.firstObject;
NSLog(@"%@", feature.messageString);
}
可以看到,CIDetector 会返回一个数组,其中包含多个 CIQRCodeFeature,对应此图片包含的多个二维码对象,CIQRCodeFeature 对象则包含了具体的编码信息。
网友评论