iOS自己实现二维码生成与扫描

阎德宇
2023-12-01

二维码的生成

 + (UIImage *)qrImageForString:(NSString *)string imageSize:(CGFloat)Imagesize logoImageSize:(CGFloat)waterImagesize{

CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];

[filter setDefaults];

NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];

[filter setValue:data forKey:@"inputMessage"];//通过kvo方式给一个字符串,生成二维码

[filter setValue:@"H" forKey:@"inputCorrectionLevel"];//设置二维码的纠错水平,越高纠错水平越高,可以污损的范围越大

CIImage *outPutImage = [filter outputImage];//拿到二维码图片

return [[self alloc] createNonInterpolatedUIImageFormCIImage:outPutImage withSize:Imagesize waterImageSize:waterImagesize];

}

- (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat) size waterImageSize:(CGFloat)waterImagesize{

CGRect extent = CGRectIntegral(image.extent);

CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent));

// 1.创建bitmap;

size_t width = CGRectGetWidth(extent) * scale;

size_t height = CGRectGetHeight(extent) * scale;

//创建一个DeviceGray颜色空间

CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();

//CGBitmapContextCreate(void * _Nullable data, size_t width, size_t height, size_t bitsPerComponent, size_t bytesPerRow, CGColorSpaceRef  _Nullable space, uint32_t bitmapInfo)

//width:图片宽度像素

//height:图片高度像素

//bitsPerComponent:每个颜色的比特值,例如在rgba-32模式下为8

//bitmapInfo:指定的位图应该包含一个alpha通道。

CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);

CIContext *context = [CIContext contextWithOptions:nil];

//创建CoreGraphics image

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);

//原图

UIImage *outputImage = [UIImage imageWithCGImage:scaledImage];

//给二维码加 logo 图

UIGraphicsBeginImageContextWithOptions(outputImage.size, NO, [[UIScreen mainScreen] scale]);

[outputImage drawInRect:CGRectMake(0,0 , size, size)];

//logo图

UIImage *waterimage = [UIImage imageNamed:@"icon_imgApp"];

//把logo图画到生成的二维码图片上,注意尺寸不要太大(最大不超过二维码图片的%30),太大会造成扫不出来

[waterimage drawInRect:CGRectMake((size-waterImagesize)/2.0, (size-waterImagesize)/2.0, waterImagesize, waterImagesize)];

UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return newPic;

}

修改二维码的颜色

- (UIImage*)imageBlackToTransparent:(UIImage*)image withRed:(CGFloat)red andGreen:(CGFloat)green andBlue:(CGFloat)blue{

const int imageWidth = image.size.width;

const int imageHeight = image.size.height;

size_t bytesPerRow = imageWidth * 4;

uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);

CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.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; //0~255

ptr[2] = green;

ptr[1] = blue;

} else {

uint8_t* ptr = (uint8_t*)pCurPtr;

ptr[0] = 0;

}

}

// 输出图片

CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, nil);

CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace, kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider, NULL, true, kCGRenderingIntentDefault);

CGDataProviderRelease(dataProvider);

UIImage* resultUIImage = [UIImage imageWithCGImage:imageRef]; // 清理空间

CGImageRelease(imageRef);

CGContextRelease(context);

CGColorSpaceRelease(colorSpace);

return resultUIImage;

}

扫码

@interface ScanQRViewController ()

//捕获设备,通常是前置摄像头,后置摄像头,麦克风(音频输入)

@property(nonatomic)AVCaptureDevice *device;

//AVCaptureDeviceInput 代表输入设备,他使用AVCaptureDevice 来初始化

@property(nonatomic)AVCaptureDeviceInput *input;

//设置输出类型为Metadata,因为这种输出类型中可以设置扫描的类型,譬如二维码

//当启动摄像头开始捕获输入时,如果输入中包含二维码,就会产生输出

@property(nonatomic)AVCaptureMetadataOutput *output;

//session:由他把输入输出结合在一起,并开始启动捕获设备(摄像头)

@property(nonatomic)AVCaptureSession *session;

//图像预览层,实时显示捕获的图像

@property(nonatomic)AVCaptureVideoPreviewLayer *previewLayer;

初始化各对象,输入输出设备结合

- (void)creatCaptureDevice{

//使用AVMediaTypeVideo 指明self.device代表视频,默认使用后置摄像头进行初始化

self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

//使用设备初始化输入

self.input = [[AVCaptureDeviceInput alloc]initWithDevice:self.device error:nil];

//生成输出对象

self.output = [[AVCaptureMetadataOutput alloc]init];

//设置代理,一旦扫描到指定类型的数据,就会通过代理输出

//在扫描的过程中,会分析扫描的内容,分析成功后就会调用代理方法在队列中输出

[self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

//生成会话,用来结合输入输出

self.session = [[AVCaptureSession alloc]init];

if ([self.session canAddInput:self.input]) {

[self.session addInput:self.input];

}

if ([self.session canAddOutput:self.output]) {

[self.session addOutput:self.output];

}

//指定当扫描到二维码的时候,产生输出

//AVMetadataObjectTypeQRCode 指定二维码

//指定识别类型一定要放到添加到session之后

[self.output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];

//设置扫描信息的识别区域,左上角为(0,0),右下角为(1,1),不设的话全屏都可以识别。设置过之后可以缩小信息扫描面积加快识别速度。

//这个属性并不好设置,整了半天也没太搞明白,到底x,y,width,height,怎么是对应的,这是我一点一点试的扫描区域,看不到只能调一下,扫一扫试试

[self.output setRectOfInterest:CGRectMake(0.1 ,0.3 , 0.4, 0.4)];

//使用self.session,初始化预览层,self.session负责驱动input进行信息的采集,layer负责把图像渲染显示

self.previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session];

self.previewLayer.frame = CGRectMake(0, 0, kScreenWidth , kScreenHeight);

self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;

[self.view.layer addSublayer:self.previewLayer];

//开始启动

[self.session startRunning];

}

实现代理方法

#pragma mark 输出的代理

//metadataObjects :把识别到的内容放到该数组中

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection

{

//停止扫描

[self.session stopRunning];

[self.timer invalidate];

self.timer = nil;

[self.lineView removeFromSuperview];

if ([metadataObjects count] >= 1) {

//数组中包含的都是AVMetadataMachineReadableCodeObject 类型的对象,该对象中包含解码后的数据

AVMetadataMachineReadableCodeObject *qrObject = [metadataObjects lastObject];

拿到扫描内容在这里进行个性化处理

NSLog(@"识别成功%@",qrObject.stringValue);

}

}

遇到的问题和解决办法

(1)二维码上加logo图的时候,图片很模糊,这是由于UIGraphicsBeginImageContextWithOptions里的 scale 造成的,由于 iPhone 的屏幕都是retina屏幕,都是2倍,3倍像素,这里的 scale 要根据屏幕来设置 即[[UIScreen mainScreen] scale]这样图片就会很清晰

(2)setRectOfInterest:设置扫描信息的识别区域,左上角为(0,0),右下角为(1,1),不设的话全屏都可以识别。设置过之后可以缩小信息扫描面积加快识别速度,原来扫描的是整个屏幕的大小,这时候只扫描一块区域,以此加快识别速度。setRectOfInterest其实设置的是一个比例分别相对于屏幕的宽和高,所以CGRectMake的4个值范围必须在0-1,对应的xy width height正好相反, 即(y/SCREEN_HEIGHT, x/SCREEN_WIDTH, height/SCREEN_HEIGHT, width/SCREEN_WIDTH),还要注意的是原点不在左上角,而是在右上角。(也许这种表述不正确,但是按照这种方式去处理,就能准确的确定区域了)

举个例子:

[self.output setRectOfInterest:CGRectMake(95/SCREEN_HEIGHT, 40/SCREEN_WIDTH, 240/SCREEN_HEIGHT, 240/SCREEN_WIDTH];
 类似资料: