阅读 158

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

前言

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

第一种:这种思路主要是采用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];复制代码
imageimage

第二种:主要操作图片的方式来处理,新建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);复制代码

image.png

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;
}复制代码

image.png

阴影处理代码片段:

// 阴影发光载体
#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


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐