CGPath复制lineJoin和miterLimit似乎没有影响。

9
我正在使用copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform‌​:)来偏移CGPath。问题在于,偏移路径引入了各种各样的锯齿状线条,似乎是斜接的结果。将miterLimit更改为0没有任何效果,使用斜角线连接也没有任何区别。
在这张图片中,有原始路径(在应用strokingWithWidth之前),使用斜接连接的偏移路径和使用斜角连接的偏移路径。为什么使用斜角连接没有任何影响?

example paths

使用斜接连接的代码(请注意,使用 CGLineJoin.round 会产生相同的结果):
let pathOffset = path.copy(strokingWithWidth: 4.0, 
                           lineCap: CGLineCap.butt,
                           lineJoin: CGLineJoin.miter,
                           miterLimit: 20.0)

context.saveGState()

context.setStrokeColor(UIColor.red.cgColor)
context.addPath(pathOffset)
context.strokePath()

context.restoreGState()

使用斜角的代码:
let pathOffset = path.copy(strokingWithWidth: 4.0, 
                           lineCap: CGLineCap.butt,
                           lineJoin: CGLineJoin.bevel,
                           miterLimit: 0.0)

context.saveGState()

context.setStrokeColor(UIColor.red.cgColor)
context.addPath(pathOffset)
context.strokePath()

context.restoreGState()
1个回答

15

这里有一条由两条线段组成的路径:

core path

如果我使用30像素的斜角连接对其进行描边,它会呈现出以下效果:

simple stroke

如果我使用相同的参数制作路径的描边副本,描边副本看起来像这样:

stroked copy

请注意那里的三角形?这是因为核心图形以简单的方式创建描边副本:沿着原始路径的每个线段进行跟踪,创建偏移15个点的复制线段。它使用直线连接这些复制线段(因为我指定了斜角连接)。在慢动作中,复制操作看起来像这样:

slow motion copy

在连接部位内部,我们得到三角形,在外部,我们得到一个平坦的棱角。当Core Graphics描绘原始路径时,这个三角形是无害的,因为Core Graphics使用非零环绕规则来填充描边。但是当您描绘描边的副本时,该三角形变得可见。现在,如果我缩小制作描边副本时使用的线宽,三角形就会变小。如果我随后增加用于绘制带有斜接连接的描边副本的线宽,并绘制描边副本,则该三角形实际上可能看起来像被填充了:

thinner stroked copy

现在,假设我用两个关节通过一条非常短的线连接来替换原始路径中的单个关节,在底部创建一个(非常小的)平坦区域:

core path with two joints

当我复制这条路径并描边时,副本内部会有两个三角形。如果我再次对该副本进行描边,它会呈现如下效果:

stroked copy of two-joint path

当你复制路径时,使用描边功能会产生奇怪的星形状,这是由于非常短的线段创建了重叠的三角形。

请注意,我使用斜角连接来进行复制。使用斜角连接进行复制也会创建隐藏的三角形,因为连接的选择只影响连接的外部,而不影响内部。

然而,连接的选择在描边时确实很重要,因为使用斜角连接会使星形更大。请参见this document,以了解连接样式对锐角外观影响如何。

因此,斜角连接使三角形的顶点突出很远,这使得重叠的三角形看起来像一颗星。如果我改用斜角连接来描边复制品,结果将如下:

stroked copy stroked with bevels

由于三角形的角被切掉了,所以这里的星星几乎看不见。

如果内部的三角形对你来说是不可接受的,你将不得不编写自己的函数(或在互联网上找到一个)以制作没有三角形的路径的描边副本,或从副本中消除三角形。

如果您的路径完全由平面线段组成,则最简单的解决方案可能是使用现有的多边形裁剪库。应用于描边副本的“union”操作应该可以消除内部三角形。请参阅此答案的示例。 请注意,这些库往往是用C++编写的,因此您可能需要编写一些Objective-C++代码,因为Swift不能直接调用C++代码。

如果你想知道我是如何生成这个答案的图形的,我是使用this Swift playground做的。


感谢你的全面回答。你画的最后一条线使用斜角连接看起来完全没问题。只是 - 我的例子使用斜角连接和斜接连接看起来完全一样,所以我想我不得不写点什么或者放弃使用我原本希望使用的这种线条样式了... - Jeshua Lacock
1
我在你发布的代码中没有看到任何设置线条连接样式的内容。你是否有类似于 context.setLineJoin(.bevel) 的代码呢? - rob mayoff
啊!您当然是对的!这确实有所帮助,但由于我的样本中的弯曲比您的线条更严重,导致了“三角形”折痕。不过,对于我的目的来说可能已经足够了,因为我不指望线条像我的示例那样扭曲。谢谢! - Jeshua Lacock
1
Rob,感谢您清晰详细的回答。我正在尝试对模糊的非填充路径进行动画处理,为此我使用copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:)将轮廓路径创建成填充路径,然后将该填充路径安装为图层的shadowPath。由于填充路径中控制点的数量随着形状的变化而变化,这些三角形伪影会对路径动画造成严重干扰,因为这些伪影会使路径动画变得混乱不堪。 - Duncan C
我考虑过编写一个更智能的笔画转填充路径的版本,它可以检测内角点的相交并去除虚假点(您将检查每对线段以查找交点,如果找到一个,则使用它代替端点)。这将是很多工作。我想知道是否已经有人编写了这样的函数? - Duncan C
@robmayoff 我想到了一种方法,通过使用外部线的交点始终跟踪描边路径的轮廓,这既可以避免您描述的三角形伪影,也可以避免具有可变点数的路径(这会对路径动画造成严重影响)。 您是否知道任何库可以创建在线上使用交点连接轮廓的描边路径(例如 copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform‌​:))? 如果找不到,我可能不得不自己编写。 - Duncan C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接