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

时间:2011-07-15 15:03:15

标签: objective-c drawing core-graphics quartz-graphics shadow

在网上搜索大约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);
}

我注意到的第一件事,阴影只在中风附近。但到目前为止,这不是问题。路径/ rect后面的阴影仍然可见,这意味着:阴影颜色会影响路径的填充颜色。填充颜​​色应为白色,而不是灰色。如何解决这个问题?

4 个答案:

答案 0 :(得分:16)

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

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

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

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

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

The described drawing on a orange background

执行上述绘制的代码是:

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

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

答案 1 :(得分:12)

根据您的评论请求,这是一个更深入的探索。请考虑以下屏幕截图(StackOverflow为我缩小它 - 它有助于查看它的完整大小。):

5 Different ways of drawing over 3 different kinds of backgrounds

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

  1. 只是中风,而不是填充,带阴影
  2. 您在原始问题的代码中发布的方式
  3. 描边和填充,未应用阴影
  4. 只是影子本身
  5. @DavidRönnqvist在答案中提出的方式。
  6. 三种不同的背景应该是自我解释的。

    你在原来的问题中说过:

      

    我注意到的第一件事,阴影只在中风附近。

    这就是我加入第一种绘图方法的原因。只有中风,没有填充,并且(因此)只有笔划被遮蔽时,这就是真正的样子。

    然后,你说:

      

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

    您的原始代码是下一个版本(#2)。您所看到的是,笔划的阴影更深而不是填充的阴影。这是因为笔触颜色的alpha为1.0且填充的alpha小于1.0。在版本#4中可能更容易看到这只是阴影 - 它在笔划边缘的周围更暗。版本#3显示没有阴影的图形。看到你可以看到红色和图像填充形状半遮挡?因此,在原始图形中,您通过对象本身看到了对象自己的阴影。

    如果这没有意义,试着想一块有点色调的玻璃杯(如果你正在摄影,想到Neutral Density Filter)。如果你把这个玻璃放在光源和另一个表面之间,然后从侧面看,只看下面的表面,你知道半透明的玻璃会投下一些阴影,但不像暗影那样暗影完全不透明(像一块纸板)。这就是你所看到的 - 你正在透过物体看它的阴影。

    版本#5是@DavidRönnqvist的方法。我在评论中谈论的愚蠢效果最容易通过查看图像背景上绘制的形状来欣赏(无论如何)。最终看起来像(在版本#5中)是形状是图像的边界,复制的部分,其被覆盖有某种半透明的白色面具。如果你回顾一下版本#3,很明显,在没有阴影的情况下,发生了什么:你正在透过下面图像的半透明形状。然后,如果你看看版本#4,你也可以清楚地知道你眼睛/相机后面的物体会投射一个阴影。从那里开始,我会争辩说,在图像版本#2上看到的情况也很明显(即使它在纯色上不太清晰)。乍一看,我的眼睛/大脑不知道它在版本#5中看到了什么 - 在我建立“复制,蒙面,部分图像漂浮在原始图像上”的心理模型之前,有一个“视觉不协调”的时刻图像“。

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

    希望这有帮助。我已经使用了我用来在GitHub生成此屏幕截图的完整示例项目。

答案 2 :(得分:2)

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);

答案 3 :(得分:1)

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];