iOS 效果处理(内阴影、外阴影、外发光、内发光、投影)

it2023-03-21  87

iOS 效果处理(内阴影,外阴影,外发光,内发光,投影)

最近在做效果处理,其中遇见了一些问题,写篇文章记录一下之前遇见的问题,这里提供两种思路来处理,效果图如下:

第一种

这种思路主要是采用Layer方式处理,采用偏移的方式达到内外阴影效果

内阴影:通过偏移X与Y 内发光:采用两个Layer偏移处理 外发光、外阴影:采用4个Layer向4个方向偏移

相关代码:

// 内发光、外发光、内阴影、外阴影 #import <QuartzCore/QuartzCore.h> NS_ASSUME_NONNULL_BEGIN /// 阴影类型 typedef NS_ENUM(NSInteger, KJShadowType) { KJShadowTypeInner, /// 内阴影 KJShadowTypeOuter, /// 外阴影 KJShadowTypeInnerShine, /// 内发光 KJShadowTypeOuterShine, /// 外发光 }; @interface KJShadowLayer : CALayer<NSCopying> /* 路径 */ @property (nonatomic, strong) UIBezierPath *kj_shadowPath; /* 颜色 */ @property (nonatomic, strong) UIColor *kj_shadowColor; /* 透明度 */ @property (nonatomic, assign) CGFloat kj_shadowOpacity; /* 半径(大小)*/ @property (nonatomic, assign) CGFloat kj_shadowRadius; /* 偏移 */ @property (nonatomic, assign) CGSize kj_shadowOffset; // ***************************** 非Layer自带参数 ********************************** /* 距离(扩展)*/ @property (nonatomic, assign) CGFloat kj_shadowDiffuse; /* 角度 */ @property (nonatomic, assign) CGFloat kj_shadowAngle; /// 初始化 - (instancetype)kj_initWithFrame:(CGRect)frame ShadowType:(KJShadowType)type; // 提供一套阴影角度算法 angele:范围(0-360)distance:距离 - (CGSize)kj_innerShadowAngle:(CGFloat)angle Distance:(CGFloat)distance; @end NS_ASSUME_NONNULL_END

绘制Layer处理代码:

#pragma mark - 绘制 - (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); /* 填充方式,枚举类型 kCGPathFill:只有填充(非零缠绕数填充),不绘制边框 kCGPathEOFill:奇偶规则填充(多条路径交叉时,奇数交叉填充,偶交叉不填充) kCGPathStroke:只有边框 kCGPathFillStroke:既有边框又有填充 kCGPathEOFillStroke:奇偶填充并绘制边框 */ if (self.kj_shadowType == KJShadowTypeOuterShine || self.kj_shadowType == KJShadowTypeOuter) { CGContextDrawPath(context, kCGPathEOFill); }else{ CGContextDrawPath(context, kCGPathEOFillStroke); } CGContextRestoreGState(context); }

偏移相关处理:

/// 修改属性 - (void)kj_changeShadowLayerValue{ 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); switch (self.kj_shadowType) { case KJShadowTypeInner: /// 内阴影 self.kj_offset = [self kj_innerShadowAngle:self.kj_shadowAngle Distance:self.kj_shadowDiffuse]; break; case KJShadowTypeInnerShine: /// 内发光 self.xxLayer.kj_path = self.kj_shadowPath; self.xxLayer.kj_color = self.kj_shadowColor; self.xxLayer.kj_radius = self.kj_shadowRadius; self.xxLayer.kj_opacity = self.kj_shadowOpacity; self.xxLayer.kj_offset = CGSizeMake(-self.kj_shadowDiffuse, -self.kj_shadowDiffuse); [self.xxLayer setNeedsDisplay]; break; case KJShadowTypeOuter: /// 外阴影 case KJShadowTypeOuterShine: /// 外发光 self.xLayer.kj_path = self.kj_shadowPath; self.xLayer.kj_color = self.kj_shadowColor; self.xLayer.kj_radius = self.kj_shadowRadius; self.xLayer.kj_opacity = self.kj_shadowOpacity; self.xLayer.kj_offset = CGSizeMake(-self.kj_shadowDiffuse, -self.kj_shadowDiffuse); [self.xLayer setNeedsDisplay]; self.xxLayer.kj_path = self.kj_shadowPath; self.xxLayer.kj_color = self.kj_shadowColor; self.xxLayer.kj_radius = self.kj_shadowRadius; self.xxLayer.kj_opacity = self.kj_shadowOpacity; self.xxLayer.kj_offset = CGSizeMake(self.kj_shadowDiffuse, -self.kj_shadowDiffuse); [self.xxLayer setNeedsDisplay]; self.xxxLayer.kj_path = self.kj_shadowPath; self.xxxLayer.kj_color = self.kj_shadowColor; self.xxxLayer.kj_radius = self.kj_shadowRadius; self.xxxLayer.kj_opacity = self.kj_shadowOpacity; self.xxxLayer.kj_offset = CGSizeMake(-self.kj_shadowDiffuse, self.kj_shadowDiffuse); [self.xxxLayer setNeedsDisplay]; break; default: break; } [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);

投影效果代码片段:

// 投影载体 #import <UIKit/UIKit.h> #import "KJEffectModel.h" NS_ASSUME_NONNULL_BEGIN @interface KJProjectionImageView : UIImageView /// 初始化 - (instancetype)kj_initWithView:(UIView*)view ExtendParameterBlock:(void(^_Nullable)(KJProjectionImageView *obj))paramblock; /// 修改效果 - (void)kj_changeProjectionInfo:(KJEffectProjectionModel*)info; #pragma mark - ExtendParameterBlock 扩展参数 @property(nonatomic,strong,readonly) KJProjectionImageView *(^kAddView)(UIView*); @property(nonatomic,strong,readonly) KJProjectionImageView *(^kFrame)(CGRect); /// 是否外界选区路径裁剪 @property(nonatomic,strong,readonly) KJProjectionImageView *(^kClipOutsidePath)(bool,UIBezierPath*); /// 效果模型初始化设置 @property(nonatomic,strong,readonly) KJProjectionImageView *(^kCreateProjectionInfo)(KJEffectProjectionModel*); @end NS_ASSUME_NONNULL_END #import "KJProjectionImageView.h" #import "UIImage+KJAccelerate.h" @interface KJProjectionImageView () @property(nonatomic,strong)KJEffectProjectionModel *projectionInfo; @property(nonatomic,strong)UIImage *resultImage; @property(nonatomic,assign)CGFloat originX,originY; @end @implementation KJProjectionImageView - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event{ return nil; } /// 初始化 - (instancetype)kj_initWithView:(UIView*)view ExtendParameterBlock:(void(^_Nullable)(KJProjectionImageView *obj))paramblock{ if (self==[super init]) { self.userInteractionEnabled = NO; self.contentMode = UIViewContentModeScaleAspectFit; self.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:0.]; /// 扩展参数回调处理 if (paramblock) paramblock(self); self.originX = self.frame.origin.x; self.originY = self.frame.origin.y; [self.superview sendSubviewToBack:self]; UIImage *kcopyImage = [self kj_captureView:[self kj_copyView:view]]; self.resultImage = [self kj_changeImageColor:UIColor.blackColor Image:kcopyImage]; if (self.projectionInfo) [self kj_changeProjectionInfo:self.projectionInfo]; } return self; } /// 修改效果 - (void)kj_changeProjectionInfo:(KJEffectProjectionModel*)info{ 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); self.alpha = info.opacity; if (info.fuzzy) { self.image = [self.resultImage kj_linearBlurryImageBlur:info.fuzzy]; }else{ self.image = self.resultImage; } } #pragma mark - privately method /// 复制UIView - (UIView*)kj_copyView:(UIView*)view{ NSData *tempArchive = [NSKeyedArchiver archivedDataWithRootObject:view]; return [NSKeyedUnarchiver unarchiveObjectWithData:tempArchive]; } /// 获取截图 - (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; } #pragma mark - ExtendParameterBlock 扩展参数 - (KJProjectionImageView *(^)(UIView*))kAddView { return ^(UIView *view){ [view addSubview:self]; return self; }; } - (KJProjectionImageView *(^)(CGRect))kFrame { return ^(CGRect rect){ self.frame = rect; return self; }; } - (KJProjectionImageView *(^)(bool,UIBezierPath*))kClipOutsidePath { return ^(bool boo,UIBezierPath *path){ if (boo) { CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init]; maskLayer.frame = CGRectMake(-self.frame.origin.x, -self.frame.origin.y, self.frame.size.width, self.frame.size.height); maskLayer.path = path.CGPath; self.layer.mask = maskLayer; } return self; }; } - (KJProjectionImageView *(^)(KJEffectProjectionModel*))kCreateProjectionInfo { return ^(KJEffectProjectionModel *a){ self.projectionInfo = a; return self; }; } @end

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 #import "KJShadowImageView.h" #import "UIImage+KJAccelerate.h" @interface KJShadowImageView () @property(nonatomic,strong)KJEffectShineModel *projectionInfo; @property(nonatomic,strong)UIBezierPath *outsidePath; @end @implementation KJShadowImageView - (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event{ return nil; } /// 初始化 - (instancetype)kj_initWithView:(UIView*)view ExtendParameterBlock:(void(^_Nullable)(KJShadowImageView *obj))paramblock{ if (self==[super init]) { self.userInteractionEnabled = NO; self.contentMode = UIViewContentModeScaleAspectFit; self.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:0.]; /// 扩展参数回调处理 if (paramblock) paramblock(self); } return self; } /// 修改效果 - (void)kj_changeShadowInfo:(KJEffectShineModel*)info ShadowType:(KJEffectImageShadowType)type{ self.alpha = info.opacity; UIColor *color = UIColor.blackColor; if (![info.hexString isEqualToString:@""] && info.hexString) { color = [UIColor kj_colorWithHexString:info.hexString]?:color; } UIImage *image = [self kj_getImageWithColor:color Extend:info.extend]; image = [image kj_linearBlurryImageBlur:info.fuzzy]; if (type == KJEffectImageShadowTypeOuter) { self.image = [self kj_outerCaptureWithImage:image]; }else if (type == KJEffectImageShadowTypeInner) { self.image = [self kj_innerCaptureWithImage:image]; }else { self.image = image; } } #pragma mark - privately method /// 生成路径图 - (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; } /// 裁剪掉路径以外区域 - (UIImage*)kj_innerCaptureWithImage:(UIImage*)image{ UIGraphicsBeginImageContextWithOptions(self.superview.bounds.size, NO, image.scale); CGContextRef context = UIGraphicsGetCurrentContext(); CGMutablePathRef outer = CGPathCreateMutable(); CGPathAddRect(outer, NULL, self.superview.bounds); CGPathAddPath(outer, NULL, self.outsidePath.CGPath); CGContextAddPath(context, outer); CGPathRelease(outer); CGContextSetBlendMode(context, kCGBlendModeClear); [image drawInRect:self.superview.bounds]; CGContextDrawPath(context, kCGPathEOFill); UIImage *newimage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return newimage; } /// 路径内部裁剪,保留路径以外区域 - (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; } #pragma mark - ExtendParameterBlock 扩展参数 - (KJShadowImageView *(^)(UIView*))kAddView { return ^(UIView *view){ [view addSubview:self]; return self; }; } - (KJShadowImageView *(^)(CGRect))kFrame { return ^(CGRect rect){ self.frame = rect; return self; }; } - (KJShadowImageView *(^)(UIBezierPath*))kOutsidePath { return ^(UIBezierPath *path){ self.outsidePath = path; return self; }; } /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ @end
阴影发光都可以是一个独立的图层(ImageView),因此他们是可以互相共同存在的。
备注:本文用到的部分函数方法和Demo,均来自三方库**KJExtensionHandler**,如有需要的朋友可自行pod 'KJExtensionHandler'引入即可

内阴影,外阴影,外发光,内发光,投影介绍就到此完毕,后面有相关再补充,写文章不容易,还请点个**小星星**传送门

最新回复(0)