1.二維碼及其原理介紹
2.二維碼生成
3.二維碼解析
二維條碼是指在一維條碼的基礎上擴展出另一維具有可讀性的條碼,使用黑白矩形圖案表示二進制數據,被設備掃描后可獲取其中所包含的信息。一維條碼的寬度記載着數據,而其長度沒有記載數據。二維條碼的長度、寬度均記載着數據。二維條碼有一維條碼沒有的“定位點”和“容錯機制”。容錯機制在即使沒有辨識到全部的條碼、或是說條碼有污損時,也可以正確地還原條碼上的信息。二維條碼的種類很多,不同的機構開發出的二維條碼具有不同的結構以及編寫、讀取方法。
堆疊式/行排式二維條碼,如,Code 16K、Code 49、PDF417(如右圖)等。
矩陣式二維碼,最流行莫過於QR CODE,二維碼的名稱是相對與一維碼來說的,比如以前的條形碼就是一個“一維碼”。它的優點有:二維碼存儲的數據量更大;可以包含數字、字符,及中文文本等混合內容;有一定的容錯性(在部分損壞以后可以正常讀取);空間利用率高等。
二維碼編碼過程
1、數據分析:確定編碼的字符類型,按相應的字符集轉換成符號字符; 選擇糾錯等級,在規格一定的條件下,糾錯等級越高其真實數據的容量越小。
2、數據編碼:將數據字符轉換為位流,每8位一個碼字,整體構成一個數據的碼字序列。其實知道這個數據碼字序列就知道了二維碼的數據內容。

下面一個案例帶你了解二維碼的編碼過程,以對數據01234567編碼為例
1)分組:012 345 67
2)轉成二進制:012→0000001100 345→0101011001 67 →1000011
3)轉成序列:0000001100 0101011001 1000011
4)字符數 轉成二進制:8→0000001000
5)加入模式指示符(上圖數字)0001:0001 0000001000 0000001100 0101011001 1000011
對於字母、中文、日文等只是分組的方式、模式等內容有所區別,基本方法是一致的。二維碼雖然比起一維條碼具有更強大的信息記載能力,但也是有容量限制,通過下面這個表格了解二維碼的容量到底有多大。

3、糾錯編碼:按需要將上面的碼字序列分塊,並根據糾錯等級和分塊的碼字,產生糾錯碼字,並把糾錯碼字加入到數據碼字序列后面,成為一個新的序列。在二維碼規格和糾錯等級確定的情況下,其實它所能容納的碼字總數和糾錯碼字數也就確定了,比如:版本10,糾錯等級時H時,總共能容納346個碼字,其中224個糾錯碼字。就是說二維碼區域中大約1/3的碼字時冗余的。對於這224個糾錯碼字,它能夠糾正112個替代錯誤(如黑白顛倒)或者224個據讀錯誤(無法讀到或者無法譯碼),這樣糾錯容量為:112/346=32.4%
4、構造最終數據信息:在規格確定的條件下,將上面產生的序列按次序放如分塊中按規定把數據分塊,然后對每一塊進行計算,得出相應的糾錯碼字區塊,把糾錯碼字區塊 按順序構成一個序列,添加到原先的數據碼字序列后面。如:D1, D12, D23, D35, D2, D13, D24, D36, … D11, D22, D33, D45, D34, D46, E1, E23,E45, E67, E2, E24, E46, E68,…
5 、構造矩陣:在構造矩陣之前,我們先來了解一個普通二維碼的基本結構。

位置探測圖形、位置探測圖形分隔符、定位圖形:用於對二維碼的定位,對每個QR碼來說,位置都是固定存在的,只是大小規格會有所差異;
校正圖形:規格確定,校正圖形的數量和位置也就確定了;
格式信息:表示改二維碼的糾錯級別,分為L、M、Q、H;
版本信息:即二維碼的規格,QR碼符號共有40種規格的矩陣(一般為黑白色),從21×21(版本1),到177×177(版本40),每一版本符號比前一版本 每邊增加4個模塊。
數據和糾錯碼字:實際保存的二維碼信息,和糾錯碼字(用於修正二維碼損壞帶來的錯誤)。
了解了二維碼的基本結構后,將探測圖形、分隔符、定位圖形、校正圖形和碼字模塊放入矩陣中,並把上面的完整序列填充到相應規格的二維碼矩陣的區域中。

6、掩膜:將掩摸圖形用於符號的編碼區域,使得二維碼圖形中的深色和淺色(黑色和白色)區域能夠比率最優的分布。
7、格式和版本信息:生成格式和版本信息放入相應區域內。版本7-40都包含了版本信息,沒有版本信息的全為0。二維碼上兩個位置包含了版本信息,它們是冗余的。版本信息共18位,6X3的矩陣,其中6位時數據為,如版本號8,數據位的信息時 001000,后面的12位是糾錯位。
這里使用蘋果的API,導入
#import <CoreImage/CoreImage.h>
/** 生成二維碼圖片 @param text 文本 @param size 圖片寬度 @return 二維碼圖片 */ -(UIImage *)getQRcodeImgByString:(NSString *)text withSize:(CGFloat)size{ //創建濾鏡--蘋果沒有將這個字符封裝成常量 CIFilter *filter=[CIFilter filterWithName:@"CIQRCodeGenerator"]; //還原路徑默認屬性 [filter setDefaults]; //設置需要生成二維碼的數據到濾鏡中,OC中要求設置的是一個二進制數據 NSData *data=[text dataUsingEncoding:NSUTF8StringEncoding]; [filter setValue:data forKey:@"InputMessage"]; //從濾鏡中取出生成好的二維碼,這里生成的二維碼不夠清晰,需要再處理 CIImage *ciImage=[filter outputImage]; return [self createNonInterpolatedUIImageFormCIImage:ciImage withSize:size]; } /** 根據CIImage生成指定大小的UIImage(相比清晰一些) @param imgae CIImage @param size 圖片寬度 @return UIImage */ -(UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)imgae withSize:(CGFloat)size{ CGRect extent=CGRectIntegral(imgae.extent); CGFloat scale=MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent)); //創建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:imgae fromRect:extent]; CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone); CGContextScaleCTM(bitmapRef, scale, scale); //繪制圖片 CGContextDrawImage(bitmapRef, extent, bitmapImage); //保存bitmap到圖片 CGImageRef scaledImage=CGBitmapContextCreateImage(bitmapRef); CGContextRelease(bitmapRef); CGImageRelease(bitmapImage); //將圖片顏色進行處理后返回 return [self imageBlackToTransparent:[UIImage imageWithCGImage:scaledImage]]; } void ProviderReleaseData (void *info, const void *data, size_t size) { free((void*)data); } //圖片顏色變換處理 - (UIImage*) imageBlackToTransparent:(UIImage*) image { // 分配內存 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); // 創建context 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) == 0xffffff00) // 將白色變成透明 // if ((*pCurPtr & 0x65815A00) == 0x65815a00) // 將背景變成透明 { uint8_t* ptr = (uint8_t*)pCurPtr; ptr[0] = 0; }else{//其他顏色修改 // 改成下面RGB值的代碼,會將圖片轉成想要的顏色 uint8_t* ptr = (uint8_t*)pCurPtr; //rgb 值0~255 ptr[3] = 226;//red ptr[2] = 89;//green ptr[1] = 72;//blue } } // 將內存轉成image CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, ProviderReleaseData); 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; }
/** 使用iOS系統自帶的視頻二維碼圖片接口,在iOS8以上可以使用 @param qrCodeImage 二維碼圖片 @return 識別出來的圖片信息 */ -(NSString *)decodeQRCodeWithImage:(UIImage *)qrCodeImage{ CIContext *context=[CIContext contextWithOptions:nil]; //此api有些問題,在一些機型上detector為nil CIDetector *detector=[CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}]; CIImage *image=[CIImage imageWithCGImage:qrCodeImage.CGImage]; NSArray *features=[detector featuresInImage:image]; CIQRCodeFeature *feature=[features firstObject]; return feature.messageString; }
