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'引入即可
内阴影,外阴影,外发光,内发光,投影介绍就到此完毕,后面有相关再补充,写文章不容易,还请点个**小星星**传送门