裁剪后无法绘制阴影 [英] Can't draw shadow after clipping

查看:112
本文介绍了裁剪后无法绘制阴影的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I needed to draw a shadow around photos of users. I drew those circle photos by drawing a circle and then clipping the context. Here's the snippet of my code:

+ (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;
}

但是在裁剪区域之后,我无法在下面绘制阴影。因此,我不得不在照片后面绘制另一个带有阴影的圆圈。

But after clipping the area I couldn't draw the shadow beneath. So I had to draw another circle with a shadow behind photos.

+ (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;
}

此方法的问题显然是它影响了我的应用程序的性能-绘图再加上两倍的圆圈,这就是表格视图。 我的问题是,在剪切上下文后如何避免绘制第二个圆并绘制阴影?我确实保存并恢复了状态,但是并没有帮助,我可能做错了。我还假设drawInRect关闭了当前路径,这就是为什么阴影不知道在哪里绘制自身的原因。我应该再打电话给CGContextAddEllipseInRect然后绘制阴影吗?

The problem with this approach is obviously it hits the performance of my app - drawing twice more circles plus it's the tableview. My question is how can I avoid drawing second circle and draw shadow after clipping the context? I did save and restore the state but it didn't help, I might be doing it wrong. I also assume that drawInRect closes the current path, that is why shadow doesn't know where to draw itself. Should I make another call to CGContextAddEllipseInRect and then draw the shadow?

推荐答案

关于性能



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

Regarding performance

Performance is interesting but often very unintuitive.

包括我自己在内的开发人员通常会对更快或更有效的事物有所了解,但很少那么简单。实际上,这是在使CPU,GPU紧张,消耗内存,代码复杂度等等之间进行权衡的问题。

Developers, myself included, often have a sense of what's faster or more efficient, but it's seldom that simple. In practicality it's a tradeoff between straining the CPU, the GPU, consuming memory, code complexity, and more.

任何执行不佳的代码都会遇到上述一个瓶颈,任何不针对该瓶颈的改进都不会改善性能码。最终哪个瓶颈会因情况而异,甚至因设备而异。即使拥有丰富的经验,也很难预测瓶颈是什么因素。确保唯一的方法是在每次更改前后都在真实设备上进行测量(阅读:使用Instruments)。

Any "poorly performing code" will have a bottleneck in one of the above, and any improvement that isn't targeting that bottleneck won't "improve performance" of that code. Which one ends up being the bottleneck will vary from case to case and may even vary from device to device. Even with extensive experience it's hard to predict what factor is the bottleneck. The only way to be sure is to measure (read: use Instruments) across real devices both before and after each change.

也就是说,您可以更改上面的代码以在与舍入后的图像相同的图像中绘制阴影,但是必须在剪切之前发生。

That said, you can change the above code to draw the shadow in the same image as the rounded image, but it has to happen before the clipping.

下面是代码的Swift版本,它就是这样做的。

Below is a Swift version of your code that does just that.

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作为比例尺事实或导致设备主屏幕的比例因子。

  • 在绘制阴影时会保存并还原上下文,因此在绘制图像时不会再次计算阴影。

此代码不会扩展图像的大小以解决阴影问题。您可能只希望在绘制图像时扩大尺寸(导致有和没有阴影的图像尺寸不同),或者总是扩大尺寸(导致没有阴影的图像周围的空白区域)。由您选择最适合您的行为。

This code doesn't extend the size of the image to account for the shadow. You'd either want to only extend the size when drawing the image (resulting in different image dimension with and without shadows) or always extend the size (resulting in empty space around images without shadows). It's up to you to pick which of those behaviors suite you best.

更多关于可能的选择及其假设性能差异的有趣推测。这些建议并非要严格接受,而是更多地说明没有单一的正确解决方案。

This is more of a fun speculation regarding possible alternatives and their hypothetical performance differences. These suggestions are not meant to be taken strictly, but more to illustrate that there is no single "correct" solution.

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

The shadow being drawn is always the same, so you could hypothetically trade CPU cycles for memory by only drawing the shadow image once draw and then reusing it. Going further, you could even include an asset (at the cost of a larger bundle and the time to read from disk) for the shadow so that you wouldn't even have to draw it the first time.

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

The image with the shadow is still transparent, meaning that it will have to blend with the background (Note: blending is almost never a problem with todays hardware. This is more hypothetical.). You could pass a background color parameter to the function and have it generate an opaque image.

剪切图像会产生成本。如果生成的图像是不透明的,则它可能包含一个资产,该资产中预先渲染了背景,圆形和圆形切口(在橙色背景顶部下方渲染)。这样,可以将轮廓图像绘制到图像上下文中而不会剪切,并且可以在阴影图像上方绘制阴影图像。

There is a cost to clipping the image. If the resulting image was opaque, it could include an asset with a background, circle, and circular cut-out pre-rendered in it (below rendered on top of an orange background). This way, the profile image could be drawn into the image context without clipping and the shadow image would be drawn above it.

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

The blending is still happening on the CPU. By adding a second layer with the pre-rendered shadow and background cut-out above, then work of blending can be moved from the CPU to the GPU.

...

与此相关的另一个方向是分层配置。您可以使用图层的拐角半径对图像进行舍入,然后使用各种阴影属性绘制阴影。只要您记得指定显式的 shadowPath ,性能差异就应该很小。

Another direction to go with this is to layer configuration. You can round the image with the layer's corner radius, and draw a shadow using the various shadow properties. As long as you remember to specify an explicit shadowPath the performance difference should be small.

您可以通过分析两种方法以及在实际设备上构建的发行版来验证最后一条语句。 ;)

You can verify that last statement by profiling both alternatives with a release build on real devices. ;)

这篇关于裁剪后无法绘制阴影的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆