附上demo地址:https://github.com/BHAreslee/shapeView
? ? ? ? 最近在開發的一個項目,要求對面部的眉毛位置進行模糊效果處理。大致分為三個步驟:1:獲取眉毛區域的關鍵點。2:通過連接關鍵點獲取一個封閉的區域。3:在這個封閉區域中增加高斯模糊效果。
目標效果:
每一個UIView之所以能夠顯示出來,其實是CALayer在起作用。兩者就相當于是畫布和畫框的關系。UIView只負責在畫布上畫一幅畫,但最終能否完整將一幅畫展示出來,則是取決于CALayer的形狀,也就是CALayer的frame。UIView主要是對顯示內容的管理而 CALayer 主要側重顯示內容的繪制。
? ? ? ? 那如何獲得一個不規則的View呢?或者說如何讓一幅畫只在不規則的區域中顯示呢?UIView只能通過修改frame改變大小和位置,似乎沒有方式去改變形狀。這個時候我們可以調整畫框即可。我們通過查看CALayer的相關屬性,可以得知CALayer有個子類叫CAShapeLayer。這個CAShapeLayer中有一個屬性是CGPathRef類的path。之前用過UIBezierPath,很快就想到這個path應該就是形狀了。我們可以通過UIBezierPath連線得到。OK,思路有了我們來實現一下吧。
實現:
在控制器中首先添加一個屬性@property (nonatomic, strong) UIImageView *imgView;
然后在viewDidLoad方法中,做一下幾部:
1:創建UIBezierPath對象,并繪制左右眉毛的區域。我以簡單的三角形替代眉毛的區域
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.創建兩個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,在控制器創建bhView對象時,傳入img,給bhView中用drawRect方法繪制。
至于為什么要創建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;
//我們只能通過調用此方法,來觸發drawRect方法。系統不讓直接調用drawRect
[bh setNeedsDisplay];
//創建CAShapeLayer對象maskLayer
CAShapeLayer* maskLayer = [CAShapeLayer layer];
//把準備好的path1賦值給maskLayer.path的path屬性
maskLayer.path = path1.CGPath;
//在將bhView的對象bh的layer設置為maskLayer
bh.layer.mask = maskLayer;
至此,我們已經得到了兩張圖,一張圖是UIImageView展示出來的,一張圖是通過bhView畫出來的,模糊后并且只在特定區域顯示。
接下來就要合并這兩張圖。
4:合并圖片,通過上下文的方式將兩個圖片繪制在一起,生成一張新圖片
CGSize size = CGSizeMake([UIScreen mainScreen].bounds.size.width,[UIScreen mainScreen].bounds.size.height);
//開啟上下文
UIGraphicsBeginImageContext(size);
//先畫完整的圖片
[self.imgView.image drawInRect:self.imgView.frame];
//再畫模糊的局部圖片。convertViewToImage:方法實現會貼在后面
[[self convertViewToImage:bh] drawInRect:bh.frame];
//拿到生成的ZImage
UIImage *ZImage = UIGraphicsGetImageFromCurrentImageContext();
//關閉圖形上下文
UIGraphicsEndImageContext();
//給展示圖賦新圖片
self.imgView.image = ZImage;
結束。看看最終效果
下面貼兩個方法
//View轉圖片
-(UIImage*)convertViewToImage:(UIView*)v{
CGSize s = v.bounds.size;
// 下面方法,第一個參數表示區域大小。第二個參數表示是否是非透明的。如果需要顯示半透明效果,需要傳NO,否則傳YES。第三個參數就是屏幕密度了
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;
//數據源提供者,Defines an opaque type that supplies Quartz with data.
CGDataProviderRef inProvider = CGImageGetDataProvider(img);
// provider’s data.
CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
//寬,高,字節/行,data
inBuffer.width = CGImageGetWidth(img);
inBuffer.height = CGImageGetHeight(img);
inBuffer.rowBytes = CGImageGetBytesPerRow(img);
inBuffer.data = (void*)CFDataGetBytePtr(inBitmapData);
//像數緩存,字節行*圖片高
pixelBuffer = malloc(CGImageGetBytesPerRow(img) * CGImageGetHeight(img));
outBuffer.data = pixelBuffer;
outBuffer.width = CGImageGetWidth(img);
outBuffer.height = CGImageGetHeight(img);
outBuffer.rowBytes = CGImageGetBytesPerRow(img);
// 第三個中間的緩存區,抗鋸齒的效果
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(@"字節組成部分:%zu",CGImageGetBitsPerComponent(img));
//顏色空間DeviceRGB
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
//用圖片創建上下文,CGImageGetBitsPerComponent(img),7,8
CGContextRef ctx = CGBitmapContextCreate(
outBuffer.data,
outBuffer.width,
outBuffer.height,
8,
outBuffer.rowBytes,
colorSpace,
CGImageGetBitmapInfo(image.CGImage));
//根據上下文,處理過的圖片,重新組件
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;
}
現在說明為什么藥自定義bhView。我剛開始也是直接創建了兩個UIImageView,一個是原圖,一個模糊部分區域的圖。然后在開上下文合并,發現結果得到的是一張全部模糊的圖。
以下直接繪制是不行的:
?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對象是沒有形狀的,雖然你只能看到兩個模糊的三角形區域。但實際上整個imgViewBlur.image都被模糊了,因為layerd的關系,我們才看不到其他區域而已。
后來我想到,通過使用UIView的drawRect方法同樣可以得到一張圖片,再通過設置layer就能夠只展現模糊的三角區域。再將bhView對象與UIImageView對象合并即可。