CoreGraphics Quartz 在透明 alpha 路径上绘制阴影

发布于 2024-11-24 03:04:33 字数 775 浏览 1 评论 0 原文

在网上搜索了大约 4 个小时没有得到答案,所以:
如何在透明路径上绘制阴影?

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(c, 2);
    CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
    CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
    CGContextSetFillColorWithColor(c, [[UIColor colorWithWhite:1.0 alpha:0.8] CGColor]);

    // Sample Path
    CGContextMoveToPoint(c, 20.0, 10.0);
    CGContextAddLineToPoint(c, 100.0, 40.0);
    CGContextAddLineToPoint(c, 40.0, 70.0);
    CGContextClosePath(c);

    CGContextDrawPath(c, kCGPathFillStroke);
}

我注意到的第一件事是,阴影仅存在于笔划周围。但到目前为止这还不是问题。路径/矩形后面的阴影仍然可见,这意味着:阴影颜色正在影响路径的填充颜色。填充颜​​色应为白色,但改为灰色。如何解决这个问题?

Searching the web for about 4 hours not getting an answer so:
How to draw a shadow on a path which has transparency?

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(c, 2);
    CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
    CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
    CGContextSetFillColorWithColor(c, [[UIColor colorWithWhite:1.0 alpha:0.8] CGColor]);

    // Sample Path
    CGContextMoveToPoint(c, 20.0, 10.0);
    CGContextAddLineToPoint(c, 100.0, 40.0);
    CGContextAddLineToPoint(c, 40.0, 70.0);
    CGContextClosePath(c);

    CGContextDrawPath(c, kCGPathFillStroke);
}

The first thing I notice, the shadow is only around the stroke. But that isn't the problem so far. The shadow behind the path/rect is still visible, which means: the shadow color is effecting the fill color of my path. The fill color should be white but instead its grey. How to solve this issue?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

焚却相思 2024-12-01 03:04:33

您必须剪切上下文并绘制两次。

首先,您创建对路径的引用,因为您必须使用它几次并保存图形上下文,以便您可以返回它。

然后,将图形上下文剪切到路径之外的唯一绘图。这是通过将您的路径添加到覆盖整个视图的路径来完成的。剪裁完成后,您可以用阴影绘制路径,以便将其绘制在外侧。

接下来,将图形上下文恢复到剪切之前的状态,并再次绘制没有阴影的路径。

它在橙色背景上看起来像这样(白色背景不是很明显)

完成上面绘制的代码是这样的:

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(c, 2);
    CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
    CGContextSetFillColorWithColor(c, [[UIColor colorWithWhite:1.0 alpha:0.5] CGColor]);

    // Sample Path
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 20.0, 10.0);
    CGPathAddLineToPoint(path, NULL, 40.0, 70.0); 
    CGPathAddLineToPoint(path, NULL, 100.0, 40.0);
    CGPathCloseSubpath(path);

    // Save the state so we can undo the shadow and clipping later
    CGContextSaveGState(c);
    { // Only for readability (so we know what are inside the save/restore scope
        CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
        CGFloat width = CGRectGetWidth(self.frame);
        CGFloat height = CGRectGetHeight(self.frame);

        // Create a mask that covers the entire frame
        CGContextMoveToPoint(c, 0, 0);
        CGContextAddLineToPoint(c, width, 0);
        CGContextAddLineToPoint(c, width, height);
        CGContextAddLineToPoint(c, 0, height);
        CGContextClosePath(c);

        // Add the path (which by even-odd rule will remove it)
        CGContextAddPath(c, path);

        // Clip to that path (drawing will only happen outside our path)
        CGContextClip(c);

        // Now draw the path in the clipped context
        CGContextAddPath(c, path);
        CGContextDrawPath(c, kCGPathFillStroke);
    }
    CGContextRestoreGState(c); // Go back to before the clipping and before the shadow

    // Draw the path without the shadow to get the transparent fill
    CGContextAddPath(c, path);
    CGContextDrawPath(c, kCGPathFillStroke);
}

如果你希望整个阴影一样强,并且不希望填充颜色的透明度使阴影变弱,那么你可以在第一次填充时使用完全不透明的颜色。它将被剪切,因此无论如何它都不会在路径内可见。它只会影响阴影。

You will have to clip the context and draw twice.

First you create a reference to your path since you will have to use it a few times and save your graphics context so you can come back to it.

Then you clip the graphics context to a only draw outside of your path. This is done by adding your path to the path that covers the entire view. Once you have clipped you draw your path with the shadow so that it's draw on the outside.

Next you restore the graphics context to how it was before you clipped and draw your path again without the shadow.

It's going to look like this on an orange background (white background wasn't very visible)

The described drawing on a orange background

The code to do the above drawing is this:

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(c, 2);
    CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
    CGContextSetFillColorWithColor(c, [[UIColor colorWithWhite:1.0 alpha:0.5] CGColor]);

    // Sample Path
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, NULL, 20.0, 10.0);
    CGPathAddLineToPoint(path, NULL, 40.0, 70.0); 
    CGPathAddLineToPoint(path, NULL, 100.0, 40.0);
    CGPathCloseSubpath(path);

    // Save the state so we can undo the shadow and clipping later
    CGContextSaveGState(c);
    { // Only for readability (so we know what are inside the save/restore scope
        CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
        CGFloat width = CGRectGetWidth(self.frame);
        CGFloat height = CGRectGetHeight(self.frame);

        // Create a mask that covers the entire frame
        CGContextMoveToPoint(c, 0, 0);
        CGContextAddLineToPoint(c, width, 0);
        CGContextAddLineToPoint(c, width, height);
        CGContextAddLineToPoint(c, 0, height);
        CGContextClosePath(c);

        // Add the path (which by even-odd rule will remove it)
        CGContextAddPath(c, path);

        // Clip to that path (drawing will only happen outside our path)
        CGContextClip(c);

        // Now draw the path in the clipped context
        CGContextAddPath(c, path);
        CGContextDrawPath(c, kCGPathFillStroke);
    }
    CGContextRestoreGState(c); // Go back to before the clipping and before the shadow

    // Draw the path without the shadow to get the transparent fill
    CGContextAddPath(c, path);
    CGContextDrawPath(c, kCGPathFillStroke);
}

If you want the entire shadow to be as strong and don't want the transparency of the fill color to make the shadow weaker then you can use a fully opaque color when filling the first time. It's going to get clipped so it won't be visible inside the path anyway. It will only affect the shadow.

忆沫 2024-12-01 03:04:33

根据您在评论中的要求,这里有更深入的探索。考虑以下屏幕截图(StackOverflow 为我缩小了它 - 它有助于查看完整尺寸。):

您在这里看到的是三种不同背景(从左到右)上的 5 种不同的绘图方法(从上到下)。 (我还将填充 alpha 从 0.8 降低到 0.5,以使效果更容易看到。)三种不同的绘制方法是(从上到下):

  1. 仅描边,而不是填充,带有阴影
  2. 您在中发布的方式原始问题中的代码
  3. 描边和填充,没有应用阴影
  4. 只是阴影本身
  5. @DavidRönnqvist 在他的答案中提出的方式。

这三种不同的背景应该是不言自明的。

你在原来的问题中说:

我注意到的第一件事是,阴影仅在笔划周围。

这就是为什么我采用了第一种绘图方法。这就是只有笔划而没有填充并且(因此)只有笔划被遮蔽时的实际情况。

然后,你说:

但到目前为止这还不是问题。路径/矩形后面的阴影是
仍然可见,这意味着:阴影颜色正在影响填充
我的道路的颜色。填充颜​​色应为白色,但改为灰色。

您的原始代码是下一个版本(#2)。您看到的是,描边的阴影比填充的阴影更暗。这是因为描边颜色的 Alpha 为 1.0,而填充的 Alpha 小于 1.0。这在版本 #4 中可能更容易看到,它只是阴影——笔画边缘的颜色更暗。版本 #3 显示了没有阴影的绘图。看到了吗,您可以看到红色和图像在形状的填充中半遮蔽吗?因此,在您的原始绘图中,您可以通过对象本身看到对象自己的阴影。

如果这没有意义,请尝试考虑一块带有色调的玻璃(如果您喜欢摄影,请考虑中性密度滤镜)。如果您将玻璃放在光源和另一个表面之间,然后从侧面观察并仅观察下表面,您就会知道半透明玻璃会投射一些阴影,但不会像其他东西那样暗完全不透明(就像一块纸板)。这就是你所看到的——你透过物体看到它的影子。

版本#5 是@DavidRönnqvist 的方法。通过查看图像背景上绘制的形状,我在评论中谈论的欺骗眼睛的效果最容易欣赏(无论如何,对我来说)。它最终看起来像(在版本#5中)是形状是图像的带边框的复制部分,上面覆盖着某种半透明的白色蒙版。如果您回顾一下版本#3,在没有阴影的情况下,很清楚发生了什么:您正在透过半透明形状查看下面的图像。然后,如果您查看版本#4,您会发现眼睛/相机后面的物体投射了阴影。从那里开始,我认为,当在图像上查看版本 #2 时,这也是很清楚的(即使在纯色上不太清楚)。乍一看,我的眼睛/大脑不知道它在版本 #5 中看到的是什么——在我建立“复制、屏蔽、漂浮在原始图像之上的部分图像”的心理模型之前,有一个“视觉不和谐”的时刻图像。”

因此,如果您想要这种效果(#5),那么大卫的解决方案将会非常有效。我只是想指出这是一种非直观的效果。

希望这有帮助。我已将用于生成此屏幕截图的完整示例项目放在 GitHub

Per your request in comments, here's a more in-depth exploration. Consider the following screenshot (StackOverflow shrinks it for me -- it helps to look at it full size.):

5 Different ways of drawing over 3 different kinds of backgrounds

What you're seeing here is 5 different drawing approaches (top to bottom) over three different backgrounds (left to right). (I've also dropped the fill alpha from 0.8 to 0.5 to make the effects easier to see.) The three different drawing approaches are (top to bottom):

  1. Just the stroke, not the fill, with a shadow
  2. The way you posted in the code in your original question
  3. The stroke and fill, with no shadow applied
  4. Just the shadow, by itself
  5. The way @DavidRönnqvist proposed in his answer.

The three different backgrounds should be self explanatory.

You said in your original question:

The first thing I notice, the shadow is only around the stroke.

This is why I included the first drawing approach. That's what it really looks like when there is just the stroke, with no fill, and (therefore) only the stroke is being shadowed.

Then, you said:

But that isn't the problem so far. The shadow behind the path/rect is
still visible, which means: the shadow color is effecting the fill
color of my path. The fill color should be white but instead its grey.

Your original code is the next version (#2). What you're seeing there is that the shadow for the stroke is darker than the shadow for the fill. This is because the stroke color's alpha is 1.0 and the fill's alpha is less than 1.0. This might be easier to see in version #4 which is just the shadow -- it's darker around the edge where the stroke is. Version #3 shows the drawing without a shadow. See you you can see the red and the image semi-obsurced in the fill of the shape? So in your original drawing you're seeing the object's own shadow through the object itself.

If that's not making sense, try thinking of a piece of glass that's got a tint to it (if you're into photography, think of a Neutral Density Filter). If you hold that glass between a light source and another surface, and then peek from the side and look just at the lower surface, you know that the semi-transparent glass is going to cast some shadow, but not as dark a shadow as something completely opaque (like a piece of cardboard). This is what you're seeing -- you're looking through the object at it's shadow.

Version #5 is @DavidRönnqvist's approach. The eye-fooling effect I was talking about in my comment is easiest to appreciate (for me, anyway) by looking at the shapes drawn over the image background. What it ends up looking like (in version #5) is that the shape is a bordered, copied, portion of the image that's been overlaid with a semi-transparent white mask of some sort. If you look back at version #3, it's clear, in the absence of the shadow, what's going on: you're looking through the semi-transparent shape at the image beneath. Then if you look at version #4, it's also clear that you have a shadow being cast by an object that's behind your eye/camera. From there, I would argue that that's also clear when looking at version #2 over the image what's going on (even if it's less clear over a solid color). At first glance, my eye/brain doesn't know what it's looking at in version #5 -- there's a moment of "visual dissonance" before I establish the mental model of "copied, masked, portion of the image floating above the original image."

So if that effect (#5) was what you were going for, then David's solution will work great. I just wanted to point out that it's sort of a non-intuitive effect.

Hope this is helpful. I've put the complete sample project I used to generate this screenshot on GitHub.

つ可否回来 2024-12-01 03:04:33
CGFloat lineWidth = 2.0f;
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
CGContextSetLineWidth(c, lineWidth);
CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
CGContextAddRect(c, someRect);
CGContextDrawPath(c, kCGPathStroke);
CGContextRestoreGState(c);
someRect.origin.x += lineWidth/2;
someRect.origin.y += lineWidth/2;
someRect.size.width -= lineWidth;
someRect.size.height -= lineWidth;
CGContextClearRect(c, someRect);
CGContextSetFillColorWithColor(c, [[[UIColor whiteColor] colorWithAlphaComponent:0.8] CGColor]);
CGContextAddRect(c, someRect);  
CGContextDrawPath(c, kCGPathFill);
CGFloat lineWidth = 2.0f;
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextSaveGState(c);
CGContextSetLineWidth(c, lineWidth);
CGContextSetStrokeColorWithColor(c, [[UIColor whiteColor] CGColor]);
CGContextSetShadowWithColor(c, CGSizeMake(0, 5), 5.0, [[UIColor blackColor]CGColor]);
CGContextAddRect(c, someRect);
CGContextDrawPath(c, kCGPathStroke);
CGContextRestoreGState(c);
someRect.origin.x += lineWidth/2;
someRect.origin.y += lineWidth/2;
someRect.size.width -= lineWidth;
someRect.size.height -= lineWidth;
CGContextClearRect(c, someRect);
CGContextSetFillColorWithColor(c, [[[UIColor whiteColor] colorWithAlphaComponent:0.8] CGColor]);
CGContextAddRect(c, someRect);  
CGContextDrawPath(c, kCGPathFill);
梦开始←不甜 2024-12-01 03:04:33
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor: [NSColor blackColor]];
[shadow setShadowOffset: NSMakeSize(2.1, -3.1)];
[shadow setShadowBlurRadius: 5];

NSBezierPath* bezierPath = [NSBezierPath bezierPath];
[bezierPath moveToPoint: NSMakePoint(12.5, 6.5)];
[bezierPath curveToPoint: NSMakePoint(52.5, 8.5) controlPoint1: NSMakePoint(40.5, 13.5) controlPoint2: NSMakePoint(52.5, 8.5)];
[bezierPath lineToPoint: NSMakePoint(115.5, 13.5)];
[bezierPath lineToPoint: NSMakePoint(150.5, 6.5)];
[bezierPath lineToPoint: NSMakePoint(201.5, 13.5)];
[bezierPath lineToPoint: NSMakePoint(222.5, 8.5)];
[NSGraphicsContext saveGraphicsState];
[shadow set];
[[NSColor blackColor] setStroke];
[bezierPath setLineWidth: 1];
[bezierPath stroke];
[NSGraphicsContext restoreGraphicsState];
NSShadow* shadow = [[NSShadow alloc] init];
[shadow setShadowColor: [NSColor blackColor]];
[shadow setShadowOffset: NSMakeSize(2.1, -3.1)];
[shadow setShadowBlurRadius: 5];

NSBezierPath* bezierPath = [NSBezierPath bezierPath];
[bezierPath moveToPoint: NSMakePoint(12.5, 6.5)];
[bezierPath curveToPoint: NSMakePoint(52.5, 8.5) controlPoint1: NSMakePoint(40.5, 13.5) controlPoint2: NSMakePoint(52.5, 8.5)];
[bezierPath lineToPoint: NSMakePoint(115.5, 13.5)];
[bezierPath lineToPoint: NSMakePoint(150.5, 6.5)];
[bezierPath lineToPoint: NSMakePoint(201.5, 13.5)];
[bezierPath lineToPoint: NSMakePoint(222.5, 8.5)];
[NSGraphicsContext saveGraphicsState];
[shadow set];
[[NSColor blackColor] setStroke];
[bezierPath setLineWidth: 1];
[bezierPath stroke];
[NSGraphicsContext restoreGraphicsState];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文