剪裁后无法绘制阴影

时间:2016-04-21 08:42:39

标签: ios objective-c core-graphics quartz-graphics

我需要在用户的照片周围画一个阴影。我通过绘制圆圈然后剪切上下文来绘制这些圆形照片。这是我的代码片段:

+ (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然后绘制阴影吗?

enter image description here

1 个答案:

答案 0 :(得分:3)

关于表现

表现很有趣,但往往非常不直观。

开发人员(包括我自己)经常会感觉更快或更有效,但很少这么简单。实际上,它是在使CPU,GPU,消耗内存,代码复杂性等紧张的过程中的权衡。

任何“效果不佳的代码”都会在上述某个方面遇到瓶颈,任何未针对该瓶颈的改进都不会“提高该代码的性能”。哪一个最终成为瓶颈因情况而异,甚至可能因设备而异。即使有丰富的经验,也很难预测到什么因素是瓶颈。唯一可以确定的方法是在每次更改之前和之后测量(读取:使用仪器)实际设备。

在同一图像中绘制阴影

也就是说,您可以更改上面的代码以在与圆形图像相同的图像中绘制阴影,但必须在剪切之前进行。

下面是你的代码的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会导致设备主屏幕的比例因子。
  • 绘制阴影时保存并恢复上下文,以便在绘制图像时不再计算阴影。

此代码扩展图像的大小以考虑阴影。您要么只想在绘制图像时扩展尺寸(导致带阴影和不带阴影的图像尺寸不同),要么总是扩大尺寸(导致图像周围没有阴影的空白区域)。您可以选择哪种行为最适合您。

考虑的替代方案

对于可能的替代方案及其假设的性能差异,这是一个有趣的推测。这些建议并不是严格意义上的,而是为了说明没有单一的“正确”解决方案。

绘制的阴影始终是相同的,因此您可以通过仅绘制阴影图像然后重新使用它来假设交换CPU周期的内存。更进一步,您甚至可以为阴影包含一个资产(以更大的捆绑包和从磁盘读取的时间为代价),这样您甚至不必在第一次绘制它。

带阴影的图像仍然是透明的,这意味着它必须与背景混合(注意:混合对于今天的硬件来说几乎不是问题。这是更假设的。)。您可以将背景颜色参数传递给函数,并使其生成不透明图像。

削减图像需要付出代价。如果生成的图像不透明,则可能包含一个资源,其中包含背景,圆形和预先渲染的圆形切口(下方呈现在橙色背景上)。这样,可以将轮廓图像绘制到图像上下文中而不进行剪切,并且阴影图像将被绘制在其上方。

enter image description here

混合仍然在CPU上发生。通过在上面添加带有预渲染阴影和背景剪切的第二层,可以将混合工作从CPU移动到GPU。

等等......

另一个方向是层配置。您可以使用图层的圆角半径对图像进行圆角处理,并使用各种阴影属性绘制阴影。只要您记得指定明确的shadowPath,性能差异应该很小。

您可以通过分析验证最后一个语句,并在实际设备上构建版本。 ;)