附上demo地址:https://github.com/BHAreslee/shapeView
? ? ? ? 最近在開發(fā)的一個項目怜奖,要求對面部的眉毛位置進(jìn)行模糊效果處理纱皆。大致分為三個步驟:1:獲取眉毛區(qū)域的關(guān)鍵點嗤无。2:通過連接關(guān)鍵點獲取一個封閉的區(qū)域。3:在這個封閉區(qū)域中增加高斯模糊效果蹬音。
目標(biāo)效果:
每一個UIView之所以能夠顯示出來坦报,其實是CALayer在起作用库说。兩者就相當(dāng)于是畫布和畫框的關(guān)系。UIView只負(fù)責(zé)在畫布上畫一幅畫片择,但最終能否完整將一幅畫展示出來潜的,則是取決于CALayer的形狀,也就是CALayer的frame字管。UIView主要是對顯示內(nèi)容的管理而 CALayer 主要側(cè)重顯示內(nèi)容的繪制啰挪。
? ? ? ? 那如何獲得一個不規(guī)則的View呢?或者說如何讓一幅畫只在不規(guī)則的區(qū)域中顯示呢嘲叔?UIView只能通過修改frame改變大小和位置亡呵,似乎沒有方式去改變形狀。這個時候我們可以調(diào)整畫框即可硫戈。我們通過查看CALayer的相關(guān)屬性锰什,可以得知CALayer有個子類叫CAShapeLayer。這個CAShapeLayer中有一個屬性是CGPathRef類的path丁逝。之前用過UIBezierPath汁胆,很快就想到這個path應(yīng)該就是形狀了。我們可以通過UIBezierPath連線得到果港。OK沦泌,思路有了我們來實現(xiàn)一下吧。
實現(xiàn):
在控制器中首先添加一個屬性@property (nonatomic, strong) UIImageView *imgView;
然后在viewDidLoad方法中辛掠,做一下幾部:
1:創(chuàng)建UIBezierPath對象谢谦,并繪制左右眉毛的區(qū)域。我以簡單的三角形替代眉毛的區(qū)域
UIBezierPath *path1 = [UIBezierPath bezierPath];
path1.lineWidth = 1;
path1.lineCapStyle = kCGLineCapRound;
path1.lineJoinStyle = kCGLineJoinRound;
//畫左眉
CGPoint p1 = CGPointMake(100, 100);
CGPoint p2 = CGPointMake(300, 100);
CGPoint p3 = CGPointMake(300, 300);
[path1 moveToPoint:p1];
[path1 addLineToPoint:p2];
[path1 addLineToPoint:p3];
[path1 closePath];
UIBezierPath *path2 = [UIBezierPath bezierPath];
//畫右眉
CGPoint p4 = CGPointMake(0, 100);
CGPoint p5 = CGPointMake(80, 90);
CGPoint p6 = CGPointMake(100, 300);
[path2 moveToPoint:p4];
[path2 addLineToPoint:p5];
[path2 addLineToPoint:p6];
[path2 closePath];
//此時我們得到了兩個path萝衩,我們用一個方法把他合為一個path回挽。讓path1包含path2。
[path1 appendPath:path2];
2.創(chuàng)建兩個UIImageView猩谊,一個用來展示原圖千劈,一個用來做模糊效果
//self.imgView用來展示原圖
self.imgView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"timg.jpeg"]];
[self.view addSubview:self.imgView];
self.imgView.frame = self.view.bounds;
//imgViewBlur用來模糊
UIImageView *imgViewBlur = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"timg.jpeg"]];
imgViewBlur.frame = self.view.bounds;
//模糊圖片操作,方法blurryImage:withBlurLevel:會在后面列出
imgViewBlur.image = [self blurryImage:imgViewBlur.image withBlurLevel:1];
3.自定義一個bhView牌捷,繼承自UIView墙牌,并添加一個屬性@property (nonatomic, strong) UIImage *img,在控制器創(chuàng)建bhView對象時暗甥,傳入img喜滨,給bhView中用drawRect方法繪制。
至于為什么要創(chuàng)建bhView撤防,會在后面說明虽风。
在bhView的.m文件中重寫drawRect
- (void)drawRect:(CGRect)rect {
self.backgroundColor = [UIColor clearColor];
[self.img drawInRect:rect];
}
bhView *bh = [[bhView alloc]initWithFrame:self.view.bounds];
bh.backgroundColor = [UIColor clearColor];
bh.img = imgViewBlur.image;
//我們只能通過調(diào)用此方法,來觸發(fā)drawRect方法。系統(tǒng)不讓直接調(diào)用drawRect
[bh setNeedsDisplay];
//創(chuàng)建CAShapeLayer對象maskLayer
CAShapeLayer* maskLayer = [CAShapeLayer layer];
//把準(zhǔn)備好的path1賦值給maskLayer.path的path屬性
maskLayer.path = path1.CGPath;
//在將bhView的對象bh的layer設(shè)置為maskLayer
bh.layer.mask = maskLayer;
至此辜膝,我們已經(jīng)得到了兩張圖无牵,一張圖是UIImageView展示出來的,一張圖是通過bhView畫出來的厂抖,模糊后并且只在特定區(qū)域顯示茎毁。
接下來就要合并這兩張圖。
4:合并圖片,通過上下文的方式將兩個圖片繪制在一起验游,生成一張新圖片
CGSize size = CGSizeMake([UIScreen mainScreen].bounds.size.width,[UIScreen mainScreen].bounds.size.height);
//開啟上下文
UIGraphicsBeginImageContext(size);
//先畫完整的圖片
[self.imgView.image drawInRect:self.imgView.frame];
//再畫模糊的局部圖片充岛。convertViewToImage:方法實現(xiàn)會貼在后面
[[self convertViewToImage:bh] drawInRect:bh.frame];
//拿到生成的ZImage
UIImage *ZImage = UIGraphicsGetImageFromCurrentImageContext();
//關(guān)閉圖形上下文
UIGraphicsEndImageContext();
//給展示圖賦新圖片
self.imgView.image = ZImage;
結(jié)束「酰看看最終效果
下面貼兩個方法
//View轉(zhuǎn)圖片
-(UIImage*)convertViewToImage:(UIView*)v{
CGSize s = v.bounds.size;
// 下面方法崔梗,第一個參數(shù)表示區(qū)域大小。第二個參數(shù)表示是否是非透明的垒在。如果需要顯示半透明效果蒜魄,需要傳NO,否則傳YES场躯。第三個參數(shù)就是屏幕密度了
UIGraphicsBeginImageContextWithOptions(s, NO, [UIScreen mainScreen].scale);
[v.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage*image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
//模糊圖片處理方法如下
- (UIImage *)blurryImage:(UIImage *)image withBlurLevel:(CGFloat)blur{? ? if (image==nil)? ? {? ? ? ? NSLog(@"error:為圖片添加模糊效果時谈为,未能獲取原始圖片");? ? ? ? return nil;? ? }? ? //模糊度,? ? if (blur < 0.025f) {? ? ? ? blur = 0.025f;? ? } else if (blur > 1.0f) {? ? ? ? blur = 1.0f;? ? }? ? ? ? //boxSize必須大于0? ? int boxSize = (int)(blur * 100);? ? boxSize -= (boxSize % 2) + 1;? ? NSLog(@"boxSize:%i",boxSize);? ? //圖像處理? ? CGImageRef img = image.CGImage;? ? //需要引入#import//圖像緩存,輸入緩存,輸出緩存
vImage_Buffer inBuffer, outBuffer;
vImage_Error error;
//像素緩存
void *pixelBuffer;
//數(shù)據(jù)源提供者踢关,Defines an opaque type that supplies Quartz with data.
CGDataProviderRef inProvider = CGImageGetDataProvider(img);
// provider’s data.
CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
//寬伞鲫,高,字節(jié)/行签舞,data
inBuffer.width = CGImageGetWidth(img);
inBuffer.height = CGImageGetHeight(img);
inBuffer.rowBytes = CGImageGetBytesPerRow(img);
inBuffer.data = (void*)CFDataGetBytePtr(inBitmapData);
//像數(shù)緩存秕脓,字節(jié)行*圖片高
pixelBuffer = malloc(CGImageGetBytesPerRow(img) * CGImageGetHeight(img));
outBuffer.data = pixelBuffer;
outBuffer.width = CGImageGetWidth(img);
outBuffer.height = CGImageGetHeight(img);
outBuffer.rowBytes = CGImageGetBytesPerRow(img);
// 第三個中間的緩存區(qū),抗鋸齒的效果
void *pixelBuffer2 = malloc(CGImageGetBytesPerRow(img) * CGImageGetHeight(img));
vImage_Buffer outBuffer2;
outBuffer2.data = pixelBuffer2;
outBuffer2.width = CGImageGetWidth(img);
outBuffer2.height = CGImageGetHeight(img);
outBuffer2.rowBytes = CGImageGetBytesPerRow(img);
//Convolves a region of interest within an ARGB8888 source image by an implicit M x N kernel that has the effect of a box filter.
error = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer2, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
error = vImageBoxConvolve_ARGB8888(&outBuffer2, &inBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
error = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
if (error) {
NSLog(@"error from convolution %ld", error);
}
//? ? NSLog(@"字節(jié)組成部分:%zu",CGImageGetBitsPerComponent(img));
//顏色空間DeviceRGB
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//用圖片創(chuàng)建上下文,CGImageGetBitsPerComponent(img),7,8
CGContextRef ctx = CGBitmapContextCreate(
outBuffer.data,
outBuffer.width,
outBuffer.height,
8,
outBuffer.rowBytes,
colorSpace,
CGImageGetBitmapInfo(image.CGImage));
//根據(jù)上下文,處理過的圖片儒搭,重新組件
CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
UIImage *returnImage = [UIImage imageWithCGImage:imageRef];
//clean up
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
free(pixelBuffer);
free(pixelBuffer2);
CFRelease(inBitmapData);
//CGColorSpaceRelease(colorSpace);? //多余的釋放
CGImageRelease(imageRef);
return returnImage;
}
現(xiàn)在說明為什么藥自定義bhView吠架。我剛開始也是直接創(chuàng)建了兩個UIImageView,一個是原圖搂鲫,一個模糊部分區(qū)域的圖傍药。然后在開上下文合并,發(fā)現(xiàn)結(jié)果得到的是一張全部模糊的圖魂仍。
以下直接繪制是不行的:
?UIGraphicsBeginImageContext(self.imgView.bounds.size);
? [self.imgView.image drawInRect:self.view.bounds];
? [imgViewBlur.image drawInRect:self.view.bounds];
? ?UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
?? UIGraphicsEndImageContext();
因為拐辽,在上下文中繪制的是imgViewBlur.imageimage對象,imgViewBlur.imageimage對象是沒有形狀的擦酌,雖然你只能看到兩個模糊的三角形區(qū)域薛训。但實際上整個imgViewBlur.image都被模糊了,因為layerd的關(guān)系仑氛,我們才看不到其他區(qū)域而已。
后來我想到,通過使用UIView的drawRect方法同樣可以得到一張圖片锯岖,再通過設(shè)置layer就能夠只展現(xiàn)模糊的三角區(qū)域介袜。再將bhView對象與UIImageView對象合并即可。