遮罩,模糊和动画会为我们的APP增色不少,现在,就让我们了解一下吧。
用Blocks绘制Images
利用下面工具函数,可以简化创建image的过程。
typedef void(^DrawingStateBlock)();
UIImage * DrawIntoImage(CGSize size, DrawingStateBlock block) {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
if (block) {
block();
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
简单的遮罩
我们可以使用Core Animation中的CAShapeLayer结合maskLayer对view进行裁剪。而这里,则介绍了Core Graphics中如何裁剪图片
上面的图片,仅在黑色圆环内的内容才能够被显示。
我们可以通过Quartz或UIKit来实现,比如通过CGContextClip()或调用UIBezierPath对象的addClip方法。
下面,我们通过addClip方法来实现这种遮罩效果:
- (UIImage *)buildSampleMaskImage {
CGSize targetSize = CGSizeMake(200, 200);
CGRect targetRect = CGRectMake(0, 0, 200, 200);
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:targetRect];
UIBezierPath *innerPath = [UIBezierPath bezierPathWithOvalInRect:RectInsetByPercent(targetRect, 0.4)];
[path appendPath:innerPath];
path.usesEvenOddFillRule = YES;
[path addClip];
UIImage *image = [UIImage imageNamed:@"Sample"];
[image drawInRect:targetRect];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
注意,当我们使用了addClip方法后,之后在Context中绘制的任何内容,都会被Clip path所限制。如果我们的Clip只是针对部分内容,可以在addClip前先saveContextState(CGContextSaveGState),在之后restoreContextState(CGContextRestoreGState)即可。
用Mask Image裁剪图片
当我们要裁剪的图片形状较为复杂时,我们可以使用mask image对Context进行修改稿,进而达到绘制在Context中的图片被裁剪的效果。注意,这里要使用的mask image必须是灰度图才有效。iOS会自动根据图片的灰度值对原图进行过滤(越黑,约不显示,越白,则越现实)。
运用mask image需要调用Quartz方法
void CGContextClipToMask(CGContextRef c, CGRect rect, CGImageRef mask)
//第一个参数表示context 指针
//第二个参数表示clip到context的区域,也是mask 图片映射到context的区域
//第三个参数表示mask的图片,对于裁剪区域Rect中的点是否变化取决于mask图中的alpha值,若alpha为0,则对应clip rect中的点为透明,如果alpha为1,则对应clip Rect中的点无变化。
- (UIImage *)buildSampleMaskImage {
CGSize targetSize = CGSizeMake(200, 200);
CGRect targetRect = CGRectMake(0, 0, 200, 200);
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);
// UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:targetRect];
// UIBezierPath *innerPath = [UIBezierPath bezierPathWithOvalInRect:RectInsetByPercent(targetRect, 0.4)];
// [path appendPath:innerPath];
// path.usesEvenOddFillRule = YES;
// [path addClip];
UIImage *maskImage = [UIImage imageNamed:@"MaskImg"];
CGContextClipToMask(UIGraphicsGetCurrentContext(), targetRect, maskImage.CGImage);
UIImage *image = [UIImage imageNamed:@"Sample"];
[image drawInRect:targetRect];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
mask image:
效果:
注意,我们这里使用了昵图网提供的灰度图,可以发现,昵图网的logo本来是在图片下方的,而现在整个的翻转到图片上方。这是因为,我们使用的是Quartz方法,而Quartz的坐标系和UIKit的坐标系是相反的,因此,在使用
CGContextClipToMask
方法时,我们需要现将UIKit的Context进行翻转,然后在转回来:
void FlipContextVertically(CGSize size)
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (context == NULL)
{
NSLog(@"Error: No context to flip");
return;
}
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, 1.0f, -1.0f);
transform = CGAffineTransformTranslate(transform, 0.0f, -size.height);
CGContextConcatCTM(context, transform);
}
- (UIImage *)buildSampleMaskImage {
CGSize targetSize = CGSizeMake(200, 200);
CGRect targetRect = CGRectMake(0, 0, 200, 200);
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);
UIImage *maskImage = [UIImage imageNamed:@"MaskImg"];
FlipContextVertically(targetSize); // 先翻转
CGContextClipToMask(UIGraphicsGetCurrentContext(), targetRect, maskImage.CGImage);
FlipContextVertically(targetSize); // clip操作之后,再转回来
UIImage *image = [UIImage imageNamed:@"Sample"];
[image drawInRect:targetRect];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
经过调整后,所得到的结果就是正确的了:
模糊
模糊效果能够给我们的APP带来一种朦胧美,在iOS中,为我们提供了如下方法来实现朦胧效果:
- Core Image API
- vImage
- iOS 7之后提供的UIKit方法
Core Image API
Core Image API 的接口相对明确,比较好理解,主要使用到了模糊blur滤镜。
iOS5.0之后就出现了Core Image的API,Core Image的API被放在CoreImage.framework库中, 在iOS和OS X平台上,Core Image都提供了大量的滤镜(Filter),在OS X上有120多种Filter,而在iOS上也有90多。
原图
- (UIImage *)coreBlurImage:(UIImage *)sourceImage withBlurNumber:(NSNumber *)blur {
CIContext *context = [CIContext context];
CIImage *inputImage = [CIImage imageWithCGImage:sourceImage.CGImage];
// 设置滤镜
CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
[blurFilter setValue:inputImage forKey:kCIInputImageKey];
[blurFilter setValue:blur forKey:@"inputRadius"];
CIImage *result = [blurFilter valueForKey:kCIOutputImageKey];
CGImageRef outPutImage = [context createCGImage:result fromRect:[result extent]];
UIImage *retImage = [UIImage imageWithCGImage:outPutImage];
CGImageRelease(outPutImage);
return retImage;
}
blur值为7时的模糊效果:
vImage
vImage属于Accelerate.Framework,需要导入 Accelerate下的 Accelerate头文件, Accelerate主要是用来做数字信号处理、图像处理相关的向量、矩阵运算的库。图像可以认为是由向量或者矩阵数据构成的,Accelerate里既然提供了高效的数学运算API,自然就能方便我们对图像做各种各样的处理 ,模糊算法使用的是vImageBoxConvolve_ARGB8888这个函数。
作者:零距离仰望星空
链接:https://www.jianshu.com/p/6dd0eab888a6
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
vImage用起来比较繁琐,网络上摘抄一段,大概了解一下就好:
+(UIImage *)boxblurImage:(UIImage *)image withBlurNumber:(CGFloat)blur
{
if (blur < 0.f || blur > 1.f) {
blur = 0.5f;
}
int boxSize = (int)(blur * 40);
boxSize = boxSize - (boxSize % 2) + 1;
CGImageRef img = image.CGImage;
vImage_Buffer inBuffer, outBuffer;
vImage_Error error;
void *pixelBuffer;
//从CGImage中获取数据
CGDataProviderRef inProvider = CGImageGetDataProvider(img);
CFDataRef inBitmapData = CGDataProviderCopyData(inProvider);
//设置从CGImage获取对象的属性
inBuffer.width = CGImageGetWidth(img);
inBuffer.height = CGImageGetHeight(img);
inBuffer.rowBytes = CGImageGetBytesPerRow(img);
inBuffer.data = (void*)CFDataGetBytePtr(inBitmapData);
pixelBuffer = malloc(CGImageGetBytesPerRow(img) * CGImageGetHeight(img));
if(pixelBuffer == NULL)
NSLog(@"No pixelbuffer");
outBuffer.data = pixelBuffer;
outBuffer.width = CGImageGetWidth(img);
outBuffer.height = CGImageGetHeight(img);
outBuffer.rowBytes = CGImageGetBytesPerRow(img);
error = vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
if (error) {
NSLog(@"error from convolution %ld", error);
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate( outBuffer.data, outBuffer.width, outBuffer.height, 8, outBuffer.rowBytes, colorSpace, kCGImageAlphaNoneSkipLast);
CGImageRef imageRef = CGBitmapContextCreateImage (ctx);
UIImage *returnImage = [UIImage imageWithCGImage:imageRef];
//clean up CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
free(pixelBuffer);
CFRelease(inBitmapData);
CGColorSpaceRelease(colorSpace);
CGImageRelease(imageRef);
return returnImage;
}
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(0, 300, SCREENWIDTH, 100)];
imageView.contentMode=UIViewContentModeScaleAspectFill;
imageView.image=[UIImage boxblurImage:image withBlurNumber:0.5];
imageView.clipsToBounds=YES;
[self.view addSubview:imageView];
作者:零距离仰望星空
链接:https://www.jianshu.com/p/6dd0eab888a6
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
UIKit
在iOS 7之后,系统默认的UI界面大量使用了模糊效果,如下拉提醒框后面的背景。
因此,Apple同样在UIKit中,加入了支持模糊效果的方法。
UIToolBar
UIToolbar的枚举样式:
typedef NS_ENUM(NSInteger, UIBarStyle) {
UIBarStyleDefault = 0,
UIBarStyleBlack = 1,
UIBarStyleBlackOpaque = 1, // Deprecated. Use UIBarStyleBlack
UIBarStyleBlackTranslucent = 2, // Deprecated. Use UIBarStyleBlack and set the translucent property to YES
}
UIVisualEffectView
在iOS8之后,Apple添加的新类UIVisualEffectView,用于支持快速的创建毛玻璃效果。
CGRect screenRect = [[UIScreen mainScreen] bounds];
//添加待模糊的图片视图
UIImageView * imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"image"]];
[imageView setFrame:screenRect];
[self.view addSubview:imageView];
// 生成特定样式的模糊效果
UIBlurEffect * blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark];
// 根据模糊效果生成模糊视图
UIVisualEffectView * effectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];
// 设定模糊区域大小
[effectView setFrame:screenRect];
[self.imageView addSubview:effectView];
这里主要是设置UIBlurEffect,共有六种:
typedef NS_ENUM(NSInteger, UIBlurEffectStyle) {
UIBlurEffectStyleExtraLight,
UIBlurEffectStyleLight,
UIBlurEffectStyleDark,
UIBlurEffectStyleExtraDark __TVOS_AVAILABLE(10_0) __IOS_PROHIBITED __WATCHOS_PROHIBITED,
UIBlurEffectStyleRegular NS_ENUM_AVAILABLE_IOS(10_0), // Adapts to user interface style
UIBlurEffectStyleProminent NS_ENUM_AVAILABLE_IOS(10_0), // Adapts to user interface style
} NS_ENUM_AVAILABLE_IOS(8_0);
Animation
Creating Display Links for Drawing
绘制动画的原理在于周期性的改变画面的内容。如果这个周期太慢,则会让人产生卡顿的感觉。
那么,我们采用什么来计算这个周期呢?推荐使用CADisplayLink,而不是NSTimer。
CADisplayLink属于QuartzCore framework,我们需要将DisplayLink添加到runloop上。
为什么不用NSTimer?
相比NSTimer,CADisplayLink有如下优点:
- CADisplayLink是和屏幕刷新率相关的,能够提供理想的动画间隔。
- 相比NSTimer,CADisplayLink更为精确,因为根据Apple文档所述,NSTimer在当前线程忙碌的情况下,可能会跳过这次scheduled fire,而大大滞后于既定的间隔。
CADisplayLink
结合Core Graphics,我们可以不断的更新所绘制的内容来达到动画效果:
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self.view selector:@selector(setNeedsDisplay)];
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
当我们link每次fire时,会调用UIView的setNeedsDisplay方法,而该方法会让iOS跟新当前的View内容,即调用drawRect方法。
默认的,link和屏幕的刷新频率一致(在表现良好的UI刷新率下,是60fps)。我们可以设置CADisplayLink的属性
link.preferredFramesPerSecond = 2
PreferredFramesPerSecond:设置每秒多少帧,CADisplayLink默认每秒运行60次,通过它的PreferredFramesPerSecond属性改变每秒运行帧数,如设置为2,意味CADisplayLink每隔一帧运行一次,有效的逻辑每秒运行30次。
当我们不需要再使用CADisplayLink时,需要将其与runloop解绑销毁,可调用如下函数:
/* Removes the receiver from the given mode of the runloop. This will
* implicitly release it when removed from the last mode it has been
* registered for. */
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
/* Removes the object from all runloop modes (releasing the receiver if
* it has been implicitly retained) and releases the 'target' object. */
- (void)invalidate;