前言
- 最近在做效果处理,其中遇见了一些问题,写篇文章记录一下之前遇见的问题,这里提供两种思路来处理
第一种:这种思路主要是采用Layer方式处理,采用偏移的方式达到内外阴影效果
内阴影:通过偏移X
与Y
内发光:采用两个layer偏移处理
外发光、外阴影:采用4个layer向4个方向偏移
拷贝效果
- (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;
}
绘制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);
}
设置属性
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 *data = [NSKeyedArchiver archivedDataWithRootObject:view];
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
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_changeColor:(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);
/// 交换像素通道从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_pathImageWithExtend:(CGFloat)extend color:(UIColor *)color{
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_outerCaptureImage:(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_innerCaptureImage:(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;
}
阴影发光都可以是一个独立的图层(UIImageView),因此他们是可以互相共同存在。
备注:本文用到的部分函数方法和Demo,均来自三方库KJCategories