標籤:
iOS開發-定製多樣式二維碼
二維碼/條碼是按照某種特定的幾何圖形按一定規律在平台(一維/二維方向上)分布的黑白相間的圖形紀錄符號資訊。使用若干個與二進位對應的幾何形體來表示文字數值資訊。
最常見的二維碼功能包括資訊擷取、網站跳轉、電商交易、手機支付等等,其擁有密度小、資訊容量大、容錯能力強、成本低、製作難度低等優點。在移動開發中,二維碼的地位也越來越重要,掌握二維碼的基本操作是重要的本領之一。
在iOS7之後,蘋果自身整合了二維碼的產生和讀取功能。產生二維碼包括以下步驟
1、匯入CoreImage/CoreImage.h標頭檔
2、使用CIFilter濾鏡類產生二維碼
3、對產生的二維碼進行加工,使其更清晰
除了上述三個步驟之外,我們還可以對二維碼進行進一步的拓展處理
1、自訂二維碼圖案顏色
2、在二維碼中心插入圓角小圖片
3、在圓角圖片下面加上一層圓角白色圖片
二維碼產生
碼農們生產代碼的同時永遠不要忘記儘可能的複用,那麼為了實現這種目的,本文的代碼通過類別拓展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; }
const CGFloat width = size.width;
const CGFloat height = size.height;
radius = MAX(5.f, radius);
radius = MIN(10.f, radius);
UIImage * img = image;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedFirst);
CGRect rect = CGRectMake(0, 0, width, height);
//繪製圓角
CGContextBeginPath(context);
addRoundRectToPath(context, rect, radius, img.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
img = [UIImage imageWithCGImage: imageMasked];
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
CGImageRelease(imageMasked);
return img;
}
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];
}
iOS開發-定製多樣式二維碼