【问题标题】:Can't draw shadow after clipping裁剪后无法绘制阴影
【发布时间】:2016-04-21 08:42:39
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

    关于性能

    性能很有趣,但通常非常不直观。

    开发人员,包括我自己,通常都知道什么是更快或更高效,但它很少那么简单。实际上,这是在 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 周期换成内存,方法是只绘制一次阴影图像,然后重用它。更进一步,您甚至可以为阴影包含一个资产(以更大的捆绑包和从磁盘读取的时间为代价),这样您甚至不必在第一次绘制它。

    带有阴影的图像仍然是透明的,这意味着它必须与背景混合(注意:混合在当今的硬件中几乎从来都不是问题。这是假设性的。)。您可以将背景颜色参数传递给函数并让它生成不透明的图像。

    裁剪图像是有成本的。如果生成的图像是不透明的,它可能包含一个带有背景、圆形和圆形切口的资源,其中预先渲染(在橙色背景之上渲染)。这样,可以将配置文件图像绘制到图像上下文中而不进行剪切,并且将在其上方绘制阴影图像。

    混合仍在 CPU 上进行。通过添加具有上述预渲染阴影和背景裁剪的第二层,可以将混合工作从 CPU 转移到 GPU。

    等等……


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

    您可以通过分析这两种备选方案以及在真实设备上发布的版本来验证最后一条语句。 ;)

    【讨论】:

    • 谢谢您,先生,我这两天一直在等待您的答复。甚至在推特上给你发了这个问题;)过去一个月我一直在关注你的博客和你在 SO 上的回答 :)。我是 Core Graphics 的新手,您的专业知识对我帮助很大。再次感谢您提供翔实的回答。现在我知道我错在哪里了。
    • 我很高兴它有帮助。关键是性能可能不直观,并且取决于具体情况。
    猜你喜欢
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 2022-10-07
    • 1970-01-01
    • 1970-01-01
    • 2012-11-08
    • 2018-07-28
    相关资源
    最近更新 更多