生成二维码
生成二维码主要用到 CIFilter,主要的函数如下
1 2 3 4 5 6
| 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,得到的效果可能如下图所示
1
| UIImage *result1 = [UIImage imageWithCIImage:outPutImage];
|
可以看到图片很模糊,此时网上大部分博客给出的解决方案如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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 *context = [CIContext contextWithOptions:nil]; CGImageRef bitmapImage = [context createCGImage:image fromRect:extent];
CGContextRef bitmapContextRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone); CGContextSetInterpolationQuality(bitmapContextRef, kCGInterpolationNone);
CGContextScaleCTM(bitmapContextRef, scale, scale);
CGContextDrawImage(bitmapContextRef, extent, bitmapImage); CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapContextRef); UIImage *outputImage = [UIImage imageWithCGImage:scaledImage]; CGContextRelease(bitmapContextRef); CGImageRelease(bitmapImage);
|
它的原理是,将 CIImage 绘制到一个灰度图上下文中(CGColorSpaceCreateDeviceGray),根据给定的 size,对画布和图像进行缩放。
这样生成的图片如下所示
可以看出二维码的确清晰了很多,但是没有博客具体说明了真正影响清晰度的原因,实际上影响清晰度的原因是这一句代码
1
| CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);
|
这里对图像插值质量做了设置,图像插值的概念如下
插值是对原图像的像素重新分布,从而来改变像素数量的一种方法。在图像放大过程中,像素也相应地增加,增加的过程就是“插值”发生作用的过程,“插值”程序自动选择信息较好的像素作为增加、弥补空白像素的空间,而并非只使用临近的像素,所以在放大图像时,图像看上去会比较平滑、干净。不过需要说明的是插值并不能增加图像信息,尽管图像尺寸变大,但效果也相对要模糊些,过程可以理解为白酒掺水。
而 CGContextSetInterpolationQuality 可能默认采用了高品质的插值函数,导致生成的图片由于进行了冗余的像素插值而变得模糊,因此将插值品质设置为 NONE,禁止进行插值操作,就可以保证像素按照原本的分布来缩放了。
猜测三种插值品质枚举值对应的插值算法:
Low: Nearest neighbor interpolation
Medium: Linear or bilinear interpolation
High: Quadratic or bicubic interpolation
给二维码加水印
主要原理就是在生成的二维码图片上 drawImage 自己的水印图片
1 2 3 4 5 6
| 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();
|
彩色二维码原理是遍历图片的每一个像素点,进行 rgb 值的改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| 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; 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); int pixelNum = imageWidth * imageHeight; uint32_t *pCurPtr = rgbImageBuf; for (int i = 0; i < pixelNum; i++, pCurPtr++){ if ((*pCurPtr & 0xFFFFFF00) < 0x99999900){ 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; } } CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, ProviderReleaseData); CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace, kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider, NULL, true, kCGRenderingIntentDefault); outputImage = [UIImage imageWithCGImage:imageRef]; CGDataProviderRelease(dataProvider); }
|
相机识别二维码
这里不赘述关于 iOS 相机的配置过程,只关注与二维码相关的步骤。
iOS 有识别二维码的 API,只需要将 AVCaptureMetadataOutput 配置到 session 的输出中,然后设置其 AVCaptureMetadataOutputObjectsDelegate 代理对象,系统检测到二维码后会调用相应的委托方法。
- 配置 AVCaptureMetadataOutput
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| - (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; }
{ if ([self.captureSession canAddOutput:self.metaDataOutput]){ [self.captureSession addOutput:self.metaDataOutput]; self.metaDataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode]; } }
|
1 2 3 4 5 6 7
| - (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 就可以检测。
1 2 3 4 5 6 7 8 9 10 11
| 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 对象则包含了具体的编码信息。