二維碼/條形碼是按照某種特定的幾何圖形按必定規律在平臺(一維/二維方向上)分佈的黑白相間的圖形紀錄符號信息。使用若干個與二進制對應的幾何形體來表示文字數值信息。html
最多見的二維碼功能包括信息獲取、網站跳轉、電商交易、手機支付等等,其擁有密度小、信息容量大、容錯能力強、成本低、製做難度低等優勢。在移動開發中,二維碼的地位也愈來愈重要,掌握二維碼的基本操做是重要的本領之一。git
在iOS7以後,蘋果自身集成了二維碼的生成和讀取功能。生成二維碼包括如下步驟github
一、導入CoreImage/CoreImage.h頭文件xcode
二、使用CIFilter濾鏡類生成二維碼微信
三、對生成的二維碼進行加工,使其更清晰ide
除了上述三個步驟以外,咱們還能夠對二維碼進行進一步的拓展處理學習
一、自定義二維碼圖案顏色網站
二、在二維碼中心插入圓角小圖片ui
三、在圓角圖片下面加上一層圓角白色圖片spa
碼農們生產代碼的同時永遠不要忘記儘量的複用,那麼爲了實現這種目的,本文的代碼經過類別拓展UIImage的方法來完成。咱們先聲明並實現一個類方法用來接收二維碼存儲數據以及二維碼尺寸的方法:
+ (UIImage *)imageOfQRFromURL: (NSString *)networkAddress codeSize: (CGFloat)codeSize {
if (!networkAddress|| (NSNull *)networkAddress == [NSNull null]) { return nil; }
codeSize = [self validateCodeSize: codeSize];
CIImage * originImage = [self createQRFromAddress: networkAddress];
UIImage * result = [UIImage imageWithCIImage: originImage];
return result;
}
在上面的代碼裏面,咱們總共作了四件事情:驗證存儲信息的有效性;驗證二維碼尺寸的合理大小;使用存儲信息生成二維碼;將二維碼轉成UIImage返回。這些方法的實現分別以下:
/*! 驗證二維碼尺寸合法性*/
+ (CGFloat)validateCodeSize: (CGFloat)codeSize
{
codeSize = MAX(160, codeSize);
codeSize = MIN(CGRectGetWidth([UIScreen mainScreen].bounds) - 80, codeSize);
return codeSize;
}
/*! 利用系統濾鏡生成二維碼圖*/
+ (CIImage *)createQRFromAddress: (NSString *)networkAddress
{
NSData * stringData = [networkAddress dataUsingEncoding: NSUTF8StringEncoding];
CIFilter * qrFilter = [CIFilter filterWithName: @"CIQRCodeGenerator"];
[qrFilter setValue: stringData forKey: @"inputMessage"];
[qrFilter setValue: @"H" forKey: @"inputCorrectionLevel"];
return qrFilter.outputImage;
}
ps:對於CIFilter想要更進一步瞭解,能夠在xcode中使用快捷鍵shift+command+0打開文檔,而後搜索core image filter reference獲取更多濾鏡的使用方法,這些濾鏡能夠用來實現相似美圖秀秀的修圖功能。
上面的代碼生成了一個粗略的二維碼圖,咱們須要對圖片再進行一次處理,使其清晰化。由於,咱們須要另一個類別方法:
/*! 對圖像進行清晰化處理*/
+ (UIImage *)excludeFuzzyImageFromCIImage: (CIImage *)image size: (CGFloat)size
{
CGRect extent = CGRectIntegral(image.extent);
CGFloat scale = MIN(size / CGRectGetWidth(extent), size / CGRectGetHeight(extent));
size_t width = CGRectGetWidth(extent) * scale;
size_t height = CGRectGetHeight(extent) * scale;
//建立灰度色調空間
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, colorSpace, (CGBitmapInfo)kCGImageAlphaNone);
CIContext * context = [CIContext contextWithOptions: nil];
CGImageRef bitmapImage = [context createCGImage: image fromRect: extent];
CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);
CGContextScaleCTM(bitmapRef, scale, scale);
CGContextDrawImage(bitmapRef, extent, bitmapImage);
CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);
CGContextRelease(bitmapRef);
CGImageRelease(bitmapImage);
CGColorSpaceRelease(colorSpace);
return [UIImage imageWithCGImage: scaledImage];
}
那麼這時候,咱們把+(UIImage *)imageOfQRFromURL: codeSize: 的最後改爲
UIImage * result =[self excludeFuzzyImageFromCIImage: originImage size: codeSize];
示例完成後生成的二維碼效果圖以下:
單一的黑白色二維碼並不必定總能知足開發的需求或者說領導的需求。比如如今的應用不少功能界面上都在朝着微信學習,這就包括了更多色彩,更多樣式的二維碼。本文將從顏色、二維碼中心小圖案這兩點入手講解如何製做相似微信生成個人二維碼的樣式。
自定義二維碼顏色的實現思路是,遍歷生成的二維碼的像素點,將其中爲白色的像素點填充爲透明色,非白色則填充爲咱們自定義的顏色。可是,這裏的白色並不僅僅指純白色,rgb值高於必定數值的灰色咱們也能夠視做白色處理。在這裏我對白色的定義爲rgb值高於0xd0d0d0的顏色值爲白色,這個值並非肯定的,你們能夠本身設置。基於顏色的設置,咱們將原有生成二維碼的方法接口改爲這樣:
+ (UIImage *)imageOfQRFromURL: (NSString *)networkAddress codeSize: (CGFloat)codeSize red: (NSUInteger)red green: (NSUInteger)green blue: (NSUInteger)blue {
if (!networkAddress || (NSNull *)networkAddress == [NSNull null]) { return nil; }
/** 顏色不能夠太接近白色*/
NSUInteger rgb = (red << 16) + (green << 8) + blue;
NSAssert((rgb & 0xffffff00) <= 0xd0d0d000, @"The color of QR code is two close to white color than it will diffculty to scan");
codeSize = [self validateCodeSize: codeSize];
CIImage * originImage = [self createQRFromAddress: networkAddress];
UIImage * progressImage = [self excludeFuzzyImageFromCIImage: originImage size: codeSize]; //到了這裏二維碼已經能夠進行掃描了
UIImage * effectiveImage = [self imageFillBlackColorAndTransparent: progressImage red: red green: green blue: blue]; //進行顏色渲染後的二維碼
return effectiveImage;
}
相較於前面的代碼,多了兩個步驟:判斷rgb的有效值;對二維碼進行顏色渲染。顏色渲染的過程包括獲取圖像的位圖上下文、像素替換、二進制圖像轉換等操做,具體代碼以下:
/*! 對生成二維碼圖像進行顏色填充*/
+ (UIImage *)imageFillBlackColorAndTransparent: (UIImage *)image red: (NSUInteger)red green: (NSUInteger)green blue: (NSUInteger)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, (CGRect){(CGPointZero), (image.size)}, image.CGImage);
//遍歷像素
int pixelNumber = imageHeight * imageWidth;
[self fillWhiteToTransparentOnPixel: rgbImageBuf pixelNum: pixelNumber red: red green: green blue: blue];
CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow, ProviderReleaseData);
CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace, kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider, NULL, true, kCGRenderingIntentDefault);
UIImage * resultImage = [UIImage imageWithCGImage: imageRef];
CGImageRelease(imageRef);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
return resultImage;
}
/*! 遍歷全部像素點進行顏色替換*/
+ (void)fillWhiteToTransparentOnPixel: (uint32_t *)rgbImageBuf pixelNum: (int)pixelNum red: (NSUInteger)red green: (NSUInteger)green blue: (NSUInteger)blue {
uint32_t * pCurPtr = rgbImageBuf;
for (int i = 0; i < pixelNum; i++, pCurPtr++) {
if ((*pCurPtr & 0xffffff00) < 0xd0d0d000) {
uint8_t * ptr = (uint8_t *)pCurPtr;
ptr[3] = red;
ptr[2] = green;
ptr[1] = blue;
} else {
//將白色變成透明色
uint8_t * ptr = (uint8_t *)pCurPtr;
ptr[0] = 0;
}
}
}
void ProviderReleaseData(void * info, const void * data, size_t size) {
free((void *)data);
}
ps:在修改代碼以前,應該想清楚是否須要刪除原有代碼。相似這種二維碼的擴展,舊的二維碼生成接口能夠留下來,而後在其中調用多參數的全能構造器(Designated Initializer)。
這時候距離微信還差一小步,咱們要在二維碼的中心位置插入咱們的小頭像,最直接的方式是加載完咱們的頭像後,直接drawInRect:。這種實現方法是正確的,可是在咱們畫上去以前,咱們還須要對圖像進行圓角處理。(省事的可能直接用imageView加載頭像,而後設置頭像的cornerRadius,這個也能實現效果)。
到了這個時候,咱們須要一個更多參數的二維碼生成方法接口了,此次新增的參數應該包括插入圖片、圓角半徑這些參數,所以方法以下:
+ (UIImage *)imageOfQRFromURL: (NSString *)networkAddress codeSize: (CGFloat)codeSize red: (NSUInteger)red green: (NSUInteger)green blue: (NSUInteger)blue insertImage: (UIImage *)insertImage roundRadius: (CGFloat)roundRadius {
if (!networkAddress || (NSNull *)networkAddress == [NSNull null]) { return nil; }
/** 顏色不能夠太接近白色*/
NSUInteger rgb = (red << 16) + (green << 8) + blue;
NSAssert((rgb & 0xffffff00) <= 0xd0d0d000, @"The color of QR code is two close to white color than it will diffculty to scan");
codeSize = [self validateCodeSize: codeSize];
CIImage * originImage = [self createQRFromAddress: networkAddress];
UIImage * progressImage = [self excludeFuzzyImageFromCIImage: originImage size: codeSize]; //到了這裏二維碼已經能夠進行掃描了
UIImage * effectiveImage = [self imageFillBlackColorAndTransparent: progressImage red: red green: green blue: blue]; //進行顏色渲染後的二維碼
return [self imageInsertedImage: effectiveImage insertImage: insertImage radius: roundRadius];
}
此次的生成方法一樣也只須要進行一次額外的調用方法操做,在插入圖片的時候咱們須要注意,相似微信的圖中圖二維碼中間的小頭像是有一個圓角的白色邊緣的,這個邊緣的加入讓頭像顯示的更加天然。那麼要完成這個效果,我額外在項目中加入了一張白色背景的小圖,一樣對這張圖片進行圓角化處理,而後加在頭像的下面。做爲頭像下方的白色背景圖像尺寸應該大於頭像圖。製做畫中畫效果的具體實現以下:
/*! 在二維碼原圖中心位置插入圓角圖像*/
+ (UIImage *)imageInsertedImage: (UIImage *)originImage insertImage: (UIImage *)insertImage radius: (CGFloat)radius {
if (!insertImage) { return originImage; }
insertImage = [UIImage imageOfRoundRectWithImage: insertImage size: insertImage.size radius: radius];
UIImage * whiteBG = [UIImage imageNamed: @"whiteBG"];
whiteBG = [UIImage imageOfRoundRectWithImage: whiteBG size: whiteBG.size radius: radius];
//白色邊緣寬度
const CGFloat whiteSize = 2.f;
CGSize brinkSize = CGSizeMake(originImage.size.width / 4, originImage.size.height / 4);
CGFloat brinkX = (originImage.size.width - brinkSize.width) * 0.5;
CGFloat brinkY = (originImage.size.height - brinkSize.height) * 0.5;
CGSize imageSize = CGSizeMake(brinkSize.width - 2 * whiteSize, brinkSize.height - 2 * whiteSize);
CGFloat imageX = brinkX + whiteSize;
CGFloat imageY = brinkY + whiteSize;
UIGraphicsBeginImageContext(originImage.size);
[originImage drawInRect: (CGRect){ 0, 0, (originImage.size) }];
[whiteBG drawInRect: (CGRect){ brinkX, brinkY, (brinkSize) }];
[insertImage drawInRect: (CGRect){ imageX, imageY, (imageSize) }];
UIImage * resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultImage;
}
+ (UIImage *)imageOfRoundRectWithImage: (UIImage *)image size: (CGSize)size radius: (CGFloat)radius
{
if (!image) {
return nil;
}
int w = size.width;
int h = size.height;
//最大圓角只能設置20
radius = MAX(20.f, radius);
UIImage *img = image;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
CGRect rect = CGRectMake(0, 0, w, h);
CGContextBeginPath(context);
addRoundedRectToPath(context, rect,radius);
CGContextClosePath(context);
CGContextClip(context);
CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return [UIImage imageWithCGImage:imageMasked];
}
static void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius)
{
float fw, fh;
if (radius == 0) {
CGContextAddRect(context, rect);
return;
}
CGContextSaveGState(context);
CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM(context, radius, radius);
fw = CGRectGetWidth(rect) / radius;
fh = CGRectGetHeight(rect) / radius;
CGContextMoveToPoint(context, fw, fh/2); // Start at lower right corner
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1); // Top right corner
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1); // Top left corner
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1); // Lower left corner
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1); // Back to lower right
CGContextClosePath(context);
CGContextRestoreGState(context);
}
ps:圖像繪製圓角是經過在圖像上下文中畫出圓角矩形的路徑,而後進行裁剪,這樣就能實現圖片的圓角化。
在代碼中,對中心位置的頭像限制尺寸爲二維碼的四分之一,這個尺寸下的頭像不失清晰度,並且圖片尺寸也不至於遮蓋了二維碼的存儲數據。上面的方法均可以在頭文件中開發方法接口使用,這將實現這些代碼的複用。另外,全部本文中寫到的生成二維碼的接口都應該在頭文件中聲明,而且在其實現中調用全能方法(不該當僅僅是構造器須要遵循Designated Initializer的原則):
+ (UIImage *)imageOfQRFromURL: (NSString *)networkAddress {
return [self imageOfQRFromURL: networkAddress codeSize: 100.0f red: 0 green: 0 blue: 0 insertImage: nil roundRadius: 0.f];
}
github地址:https://github.com/JustKeepRunning/LXDTwoDimensionalBarcode