效果处理(内阴影、外阴影、外发光、内发光、投影)
前言
最近在做效果处理,其中遇见了一些问题,写篇文章记录一下之前遇见的问题,这里提供两种思路来处理
第一种:这种思路主要是采用Layer方式处理,采用偏移的方式达到内外阴影效果
内阴影:通过偏移X与Y
内发光:采用两个Layer偏移处理
外发光、外阴影:采用4个Layer向4个方向偏移
1.KJShadowLayer具有拷贝效果
/// 具有拷贝效果 - (instancetype)copyWithZone:(NSZone *)zone { KJShadowLayer *layer = [[KJShadowLayer allocWithZone:zone] init]; layer.frame = self.frame; layer.kj_path = self.kj_path; layer.kj_color = self.kj_color; layer.kj_offset = self.kj_offset; layer.kj_radius = self.kj_radius; layer.kj_opacity = self.kj_opacity; layer.kj_shadowType = self.kj_shadowType; return layer; }复制代码
2.初始化
- (instancetype)kj_initWithFrame:(CGRect)frame ShadowType:(KJShadowType)type{ if (self == [super init]) { self.frame = frame; self.drawsAsynchronously = YES; self.contentsScale = [UIScreen mainScreen].scale; self.kj_shadowType = type; self.kj_shadowColor = UIColor.blackColor; if (type == KJShadowTypeInnerShine) { self.xxLayer = [self copy]; [self addSublayer:_xxLayer]; }else if (type == KJShadowTypeOuterShine || type == KJShadowTypeOuter) { self.xLayer = [self copy]; self.xxLayer = [self copy]; self.xxxLayer = [self copy]; [self addSublayer:_xLayer]; [self addSublayer:_xxLayer]; [self addSublayer:_xxxLayer]; } } return self; }复制代码
3.绘制Layer
- (void)drawInContext:(CGContextRef)context { CGRect rect = self.bounds; if (self.borderWidth != 0) rect = CGRectInset(rect, self.borderWidth, self.borderWidth); CGContextSaveGState(context); if (self.kj_shadowType == KJShadowTypeInner || self.kj_shadowType == KJShadowTypeInnerShine) { CGContextAddPath(context, self.kj_path.CGPath); CGContextClip(context); CGMutablePathRef outer = CGPathCreateMutable(); CGPathAddRect(outer, NULL, CGRectInset(rect, -1 * rect.size.width, -1 * rect.size.height)); CGPathAddPath(outer, NULL, self.kj_path.CGPath); CGPathCloseSubpath(outer); CGContextAddPath(context, outer); CGPathRelease(outer); }else{ CGContextAddPath(context, self.kj_path.CGPath); } UIColor *color = [self.kj_color colorWithAlphaComponent:self.kj_opacity]; CGContextSetShadowWithColor(context, self.kj_offset, self.kj_radius, color.CGColor); if (self.kj_shadowType == KJShadowTypeOuterShine || self.kj_shadowType == KJShadowTypeOuter) { CGContextDrawPath(context, kCGPathEOFill); }else{ CGContextDrawPath(context, kCGPathEOFillStroke); } CGContextRestoreGState(context); }复制代码
4.设置属性
self.kj_path = self.kj_shadowPath; self.kj_color = self.kj_shadowColor; self.kj_radius = self.kj_shadowRadius; self.kj_opacity = self.kj_shadowOpacity; self.kj_offset = CGSizeMake(self.kj_shadowDiffuse, self.kj_shadowDiffuse); [self setNeedsDisplay];复制代码
第二种:主要操作图片的方式来处理,新建ImageView来承载投影、阴影等效果
1、投影 - 核心思路
1.1 - 归档复制要投影的视图(因为我只需要上面的图片,所以采用归档的方式复制一份再截图处理)
/// 复制UIView - (UIView*)kj_copyView:(UIView*)view{ NSData *tempArchive = [NSKeyedArchiver archivedDataWithRootObject:view]; return [NSKeyedUnarchiver unarchiveObjectWithData:tempArchive]; }复制代码
1.2 - 截图并修改图片颜色
/// 获取截图 - (UIImage*)kj_captureView:(UIView*)view{ UIGraphicsBeginImageContext(view.bounds.size); CGContextRef ctx = UIGraphicsGetCurrentContext(); [view.layer renderInContext:ctx]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; } /// 改变图片颜色 - (UIImage*)kj_changeImageColor:(UIColor*)color Image:(UIImage*)image{ UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM(context, 0, image.size.height); CGContextScaleCTM(context, 1.0, -1.0); CGContextSetBlendMode(context, kCGBlendModeNormal); CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height); CGContextClipToMask(context, rect, image.CGImage); [color setFill]; CGContextFillRect(context, rect); UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newImage; }复制代码
1.3 - 实现效果
距离和角度:采用偏移坐标的方式处理
CGFloat x = info.diffuse * sin(info.angle); CGFloat y = info.diffuse * cos(info.angle); self.frame = CGRectMake(self.originX+x, self.originY+y, self.width, self.height);复制代码
模糊:这里采用 Accelerate 框架里面的模糊滤镜处理,
主要函数 box滤镜vImageBoxConvolve_ARGB8888
和交换像素通道vImagePermuteChannels_ARGB8888
这里有个细节需要注意:CGImageAlphaInfo 需要使用kCGImageAlphaPremultipliedLast
枚举,从而保留透明区域(不变黑)
/// box滤镜(模糊滤镜) error = vImageBoxConvolve_ARGB8888(&inBuffer,&outBuffer,NULL,0,0,boxSize,boxSize,NULL,kvImageEdgeExtend); if (error) NSLog(@"error from convolution %ld", error); /// 交换像素通道从BGRA到RGBA const uint8_t permuteMap[] = {2, 1, 0, 3}; vImagePermuteChannels_ARGB8888(&outBuffer,&rgbOutBuffer,permuteMap,kvImageNoFlags); /// kCGImageAlphaPremultipliedLast 保留透明区域 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef ctx = CGBitmapContextCreate(rgbOutBuffer.data, rgbOutBuffer.width, rgbOutBuffer.height, 8, rgbOutBuffer.rowBytes, colorSpace, kCGImageAlphaPremultipliedLast);复制代码
2、阴影(其实阴影和发光根本原理一样)
2.1 - 生成路径图
/// 生成路径图 - (UIImage*)kj_getImageWithColor:(UIColor*)color Extend:(CGFloat)extend{ UIGraphicsBeginImageContext(self.superview.size); UIBezierPath *path = self.outsidePath; path.lineWidth = extend; path.lineCapStyle = kCGLineCapRound; path.lineJoinStyle = kCGLineCapRound; [color set]; [path stroke]; UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return img; }复制代码
2.2 - 模糊处理(处理方式和投影一致)
这里需要注意的就是生成的路径图是包含内外阴影,单独使用的话需要做裁剪处理
2.3 - 裁剪处理
外阴影:将路径内部的裁剪掉
/// 路径内部裁剪,保留路径以外区域 - (UIImage*)kj_outerCaptureWithImage:(UIImage*)image{ UIGraphicsBeginImageContextWithOptions(self.superview.bounds.size, NO, image.scale); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetBlendMode(context, kCGBlendModeClear);/// kCGBlendModeClear 裁剪部分透明 [image drawInRect:self.superview.bounds]; CGContextAddPath(context, self.outsidePath.CGPath); CGContextDrawPath(context, kCGPathEOFill); UIImage *newimage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newimage; }复制代码
内阴影:同理,将路径以外部分裁剪掉
/// 裁剪掉路径以外区域 - (UIImage*)kj_innerCaptureWithImage:(UIImage*)image{ UIGraphicsBeginImageContextWithOptions(self.superview.bounds.size, NO, image.scale); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetBlendMode(context, kCGBlendModeClear); [image drawInRect:self.superview.bounds]; UIBezierPath *path = ({ /// 镂空 UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.superview.bounds]; path.usesEvenOddFillRule = YES; [path appendPath:self.outsidePath]; path; }); CGContextAddPath(context, path.CGPath); CGContextDrawPath(context, kCGPathEOFill); UIImage *newimage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newimage; }复制代码
阴影处理代码片段:
// 阴影发光载体 #import <UIKit/UIKit.h> #import "KJEffectModel.h" NS_ASSUME_NONNULL_BEGIN /// 阴影类型 typedef NS_OPTIONS(NSInteger, KJEffectImageShadowType) { KJEffectImageShadowTypeInner,/// 内 KJEffectImageShadowTypeOuter,/// 外 KJEffectImageShadowTypeMeanwhile,/// 内外都有 }; @interface KJShadowImageView : UIImageView /// 初始化 - (instancetype)kj_initWithView:(UIView*)view ExtendParameterBlock:(void(^_Nullable)(KJShadowImageView *obj))paramblock; /// 修改效果 - (void)kj_changeShadowInfo:(KJEffectShineModel*)info ShadowType:(KJEffectImageShadowType)type; #pragma mark - ExtendParameterBlock 扩展参数 @property(nonatomic,strong,readonly) KJShadowImageView *(^kAddView)(UIView*); @property(nonatomic,strong,readonly) KJShadowImageView *(^kFrame)(CGRect); /// 外界选区路径 @property(nonatomic,strong,readonly) KJShadowImageView *(^kOutsidePath)(UIBezierPath*); @end NS_ASSUME_NONNULL_END复制代码
阴影发光都可以是一个独立的图层(ImageView),因此他们是可以互相共同存在的。
备注:本文用到的部分函数方法和Demo,均来自三方库**KJEmitterView**,如有需要的朋友可自行pod 'KJEmitterView'
引入即可
作者:茶底世界之下
链接:https://juejin.cn/post/7011309826592473125