前言
最近在做效果处理,其中遇见了一些问题,写篇文章记录一下之前遇见的问题,这里提供两种思路来处理
第一种:这种思路主要是采用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