裁剪后无法绘制阴影
Can't draw shadow after clipping
我需要在用户照片周围画阴影。我通过画一个圆圈然后剪裁上下文来画那些圆圈照片。这是我的代码片段:
+ (UIImage*)roundImage:(UIImage*)img imageView:(UIImageView*)imageView withShadow:(BOOL)shadow
{
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(context, CGRectMake(0,0, imageView.width, imageView.height));
CGContextSaveGState(context);
CGContextClip(context);
[img drawInRect:imageView.bounds];
CGContextRestoreGState(context);
if (shadow) {
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 5, [kAppColor lighterColor].CGColor);
}
CGContextDrawPath(context, kCGPathFill);
UIImage* roundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return roundImage;
}
但是在裁剪区域后我无法在下面绘制阴影。所以我不得不在照片后面画一个带阴影的圆圈。
+ (UIImage *)circleShadowFromRect:(CGRect)rect circleDiameter:(CGFloat)circleDiameter shadowColor:(UIColor*)color
{
UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGFloat circleStartPointX = CGRectGetMidX(rect) - circleDiameter * 0.5;
CGFloat circleStartPointY = CGRectGetMidY(rect) - circleDiameter * 0.5;
CGContextAddEllipseInRect(context, CGRectMake(circleStartPointX,circleStartPointY, circleDiameter, circleDiameter));
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 5, color.CGColor);
CGContextDrawPath(context, kCGPathFill);
UIImage *circle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return circle;
}
这种方法的问题显然是它影响了我的应用程序的性能 - 多画了两次圆加上它是 tableview。 我的问题是如何避免在剪切上下文后绘制第二个圆圈和绘制阴影?我确实保存并恢复了状态,但没有帮助,我可能做错了。我还假设 drawInRect 关闭了当前路径,这就是为什么阴影不知道在哪里绘制自己。我是否应该再次调用 CGContextAddEllipseInRect 然后绘制阴影?
关于性能
性能很有趣,但通常很不直观。
开发人员,包括我自己,通常都知道什么更快或更有效,但这很少那么简单。实际上,它是在 CPU、GPU、内存消耗、代码复杂度等方面进行权衡。
任何 "poorly performing code" 都会在上述其中一项中遇到瓶颈,并且任何不针对该瓶颈的改进都不会 "improve performance" 该代码。哪一个最终成为瓶颈将因情况而异,甚至可能因设备而异。即使拥有丰富的经验,也很难预测瓶颈是什么因素。唯一可以确定的方法是在每次更改之前和之后跨真实设备进行测量(阅读:使用 Instruments)。
在同一图像中绘制阴影
也就是说,您可以更改上面的代码以在与圆形图像相同的图像中绘制阴影,但它必须在裁剪之前发生。
下面是您的代码的 Swift 版本,它就是这样做的。
func roundedImage(for image: UIImage, bounds: CGRect, withShadow shadow: Bool) -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
defer {
UIGraphicsEndImageContext()
}
let context = UIGraphicsGetCurrentContext()
let circle = CGPathCreateWithEllipseInRect(bounds, nil)
if shadow {
// draw an elliptical shadow
CGContextSaveGState(context)
CGContextSetShadowWithColor(context, .zero, 5.0, UIColor.blackColor().CGColor)
CGContextAddPath(context, circle)
CGContextFillPath(context)
CGContextRestoreGState(context)
}
// clip to an elliptical shape, and draw the image
CGContextAddPath(context, circle)
CGContextClip(context)
image.drawInRect(bounds)
return UIGraphicsGetImageFromCurrentImageContext()
}
一些注意事项:
- 为比例因子传递 0.0 会导致设备主屏幕的比例因子。
- 在绘制阴影时保存和恢复上下文,这样绘制图像时就不会重新计算阴影。
此代码不会扩展图像的大小以解决阴影问题。您可能只想在绘制图像时扩展尺寸(导致有阴影和没有阴影的图像尺寸不同),或者总是扩展尺寸(导致没有阴影的图像周围为空 space)。您可以选择最适合您的行为。
要考虑的替代方案
这更像是关于可能的替代方案及其假设的性能差异的有趣推测。这些建议并不是要严格采纳,更多的是为了说明没有单一的"correct"解决方案。
绘制的阴影始终相同,因此您可以通过仅绘制一次阴影图像然后重新使用它来假设用 CPU 周期换取内存。更进一步,您甚至可以为阴影包含一个资产(以更大的捆绑包和从磁盘读取时间为代价),这样您甚至不必在第一次绘制它。
带有阴影的图像仍然是透明的,这意味着它必须与背景混合(注意:混合几乎从来不是当今硬件的问题。这更像是假设。).您可以将背景颜色参数传递给函数并让它生成不透明图像。
裁剪图片需要付费。如果生成的图像是不透明的,它可能包含一个带有背景、圆圈和预渲染的圆形切口的资产(下方渲染在橙色背景之上)。通过这种方式,可以在不裁剪的情况下将个人资料图像绘制到图像上下文中,并且将在其上方绘制阴影图像。
混合仍在 CPU 上进行。通过在上面添加带有预渲染阴影和背景剪切的第二层,然后混合工作可以从 CPU 转移到 GPU。
等等...
另一个方向是图层配置。您可以使用图层的角半径对图像进行圆角处理,并使用各种阴影属性绘制阴影。只要您记得明确指定 shadowPath
,性能差异应该很小。
您可以通过分析 两种备选方案并在真实设备上发布版本来验证最后的陈述。 ;)
我需要在用户照片周围画阴影。我通过画一个圆圈然后剪裁上下文来画那些圆圈照片。这是我的代码片段:
+ (UIImage*)roundImage:(UIImage*)img imageView:(UIImageView*)imageView withShadow:(BOOL)shadow
{
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(context, CGRectMake(0,0, imageView.width, imageView.height));
CGContextSaveGState(context);
CGContextClip(context);
[img drawInRect:imageView.bounds];
CGContextRestoreGState(context);
if (shadow) {
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 5, [kAppColor lighterColor].CGColor);
}
CGContextDrawPath(context, kCGPathFill);
UIImage* roundImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return roundImage;
}
但是在裁剪区域后我无法在下面绘制阴影。所以我不得不在照片后面画一个带阴影的圆圈。
+ (UIImage *)circleShadowFromRect:(CGRect)rect circleDiameter:(CGFloat)circleDiameter shadowColor:(UIColor*)color
{
UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGFloat circleStartPointX = CGRectGetMidX(rect) - circleDiameter * 0.5;
CGFloat circleStartPointY = CGRectGetMidY(rect) - circleDiameter * 0.5;
CGContextAddEllipseInRect(context, CGRectMake(circleStartPointX,circleStartPointY, circleDiameter, circleDiameter));
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 5, color.CGColor);
CGContextDrawPath(context, kCGPathFill);
UIImage *circle = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return circle;
}
这种方法的问题显然是它影响了我的应用程序的性能 - 多画了两次圆加上它是 tableview。 我的问题是如何避免在剪切上下文后绘制第二个圆圈和绘制阴影?我确实保存并恢复了状态,但没有帮助,我可能做错了。我还假设 drawInRect 关闭了当前路径,这就是为什么阴影不知道在哪里绘制自己。我是否应该再次调用 CGContextAddEllipseInRect 然后绘制阴影?
关于性能
性能很有趣,但通常很不直观。
开发人员,包括我自己,通常都知道什么更快或更有效,但这很少那么简单。实际上,它是在 CPU、GPU、内存消耗、代码复杂度等方面进行权衡。
任何 "poorly performing code" 都会在上述其中一项中遇到瓶颈,并且任何不针对该瓶颈的改进都不会 "improve performance" 该代码。哪一个最终成为瓶颈将因情况而异,甚至可能因设备而异。即使拥有丰富的经验,也很难预测瓶颈是什么因素。唯一可以确定的方法是在每次更改之前和之后跨真实设备进行测量(阅读:使用 Instruments)。
在同一图像中绘制阴影
也就是说,您可以更改上面的代码以在与圆形图像相同的图像中绘制阴影,但它必须在裁剪之前发生。
下面是您的代码的 Swift 版本,它就是这样做的。
func roundedImage(for image: UIImage, bounds: CGRect, withShadow shadow: Bool) -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
defer {
UIGraphicsEndImageContext()
}
let context = UIGraphicsGetCurrentContext()
let circle = CGPathCreateWithEllipseInRect(bounds, nil)
if shadow {
// draw an elliptical shadow
CGContextSaveGState(context)
CGContextSetShadowWithColor(context, .zero, 5.0, UIColor.blackColor().CGColor)
CGContextAddPath(context, circle)
CGContextFillPath(context)
CGContextRestoreGState(context)
}
// clip to an elliptical shape, and draw the image
CGContextAddPath(context, circle)
CGContextClip(context)
image.drawInRect(bounds)
return UIGraphicsGetImageFromCurrentImageContext()
}
一些注意事项:
- 为比例因子传递 0.0 会导致设备主屏幕的比例因子。
- 在绘制阴影时保存和恢复上下文,这样绘制图像时就不会重新计算阴影。
此代码不会扩展图像的大小以解决阴影问题。您可能只想在绘制图像时扩展尺寸(导致有阴影和没有阴影的图像尺寸不同),或者总是扩展尺寸(导致没有阴影的图像周围为空 space)。您可以选择最适合您的行为。
要考虑的替代方案
这更像是关于可能的替代方案及其假设的性能差异的有趣推测。这些建议并不是要严格采纳,更多的是为了说明没有单一的"correct"解决方案。
绘制的阴影始终相同,因此您可以通过仅绘制一次阴影图像然后重新使用它来假设用 CPU 周期换取内存。更进一步,您甚至可以为阴影包含一个资产(以更大的捆绑包和从磁盘读取时间为代价),这样您甚至不必在第一次绘制它。
带有阴影的图像仍然是透明的,这意味着它必须与背景混合(注意:混合几乎从来不是当今硬件的问题。这更像是假设。).您可以将背景颜色参数传递给函数并让它生成不透明图像。
裁剪图片需要付费。如果生成的图像是不透明的,它可能包含一个带有背景、圆圈和预渲染的圆形切口的资产(下方渲染在橙色背景之上)。通过这种方式,可以在不裁剪的情况下将个人资料图像绘制到图像上下文中,并且将在其上方绘制阴影图像。
混合仍在 CPU 上进行。通过在上面添加带有预渲染阴影和背景剪切的第二层,然后混合工作可以从 CPU 转移到 GPU。
等等...
另一个方向是图层配置。您可以使用图层的角半径对图像进行圆角处理,并使用各种阴影属性绘制阴影。只要您记得明确指定 shadowPath
,性能差异应该很小。
您可以通过分析 两种备选方案并在真实设备上发布版本来验证最后的陈述。 ;)