有没有一种简单的方法可以连接两条重叠的贝塞尔曲线的切线?

Posted

技术标签:

【中文标题】有没有一种简单的方法可以连接两条重叠的贝塞尔曲线的切线?【英文标题】:Is there an easy way to connect the tangents of two overlapping Bezier curves? 【发布时间】:2018-01-12 06:12:15 【问题描述】:

我正在尝试用笔模拟笔触。我创建了一个 QPainterPath,它由两条相同的 Bezier 曲线路径组成,它们相互偏移,并在末端连接。代码大致是这样的:

path1 = QPainterPath(curvePath)
path2 = QPainterPath(curvePath).toReversed()
path1.translate(5, -5)
path2.translate(-5, 5)

strokeShape.addPath(path1)
strokeShape.connectPath(path2)
strokeShape.closeSubpath()

gc.drawPath(strokeShape)

它适用于直线和某些曲线:

但是,如果曲线更极端,路径会“裂开”并且看起来很糟糕:

在早期版本中,我沿着路径画了一堆小四边形,但我希望我能得到更平滑的曲线(和更简单的代码):

我似乎找不到任何神奇的 Qt 解决方案。 Qt 似乎不允许您沿着路径描边任意形状,除非我错过了它。

那么两个问题:

    您可以沿 QPainterPath 描边自定义形状吗? 失败 #1,是否有一种简单的方法/算法可以在 Qt 中找到三次贝塞尔曲线极端处的切线,以便我可以通过一条线或另一条曲线连接路径?

谢谢!!

【问题讨论】:

【参考方案1】:

嗯,我找到了几个解决方案,但都不是完美的。

    Drawing a line with a pixmap brush in Qt?

使用这种方法,我创建了一个 2 像素高的像素图,并将其旋转到与我的“笔”相同的角度,然后沿着线条重复绘制。不幸的是,要看起来不错,它至少需要 1000 点的分辨率:

penXformed = penPixmap.transformed(QtGui.QTransform().rotate(-40), QtCore.Qt.SmoothTransformation)
for i in range(0, 1000):
    pct = float(i) / 1000.0

    gc.drawPixmap(curvePath.pointAtPercent(pct) - QtCore.QPoint(10, 10), penXformed)

但它看起来并不平滑,而且 alpha 似乎适用于整个形状,因此您看不到重叠。可能有一些混合模式会更好,但我现在并不真正想要这个解决方案:

但是,如果我想做粉笔外观或画笔之类的事情,很高兴知道。

    可能有一种更高级的算法,但只要切线接近我想要的角度(40 度),检查切线并将曲线分成几段基本上可以工作:

但是,我需要再次检查 1000 个间隔中的百分比,以获得足够的切线来匹配。理想情况下,这条曲线只有 3 段,所以我的代码中一定有一个错误,因为我得到了不必要的段:

这个解决方案需要更多的代码,一组来找到切线:

for i in range(0, 1000):
    pct = float(i) / 1000.0
    angle = int(curvePath.angleAtPercent(pct))
    if angle >= 39 and angle <= 41:

        # l = to the left of pct, r is to the right of pct  
        (l, r) = sliceBezier(verts, pct)

        curCurve = QtGui.QPainterPath()
        curCurve.moveTo(l[0][0], l[0][1])
        curCurve.cubicTo(l[1][0], l[1][1], l[2][0], l[2][1], l[3][0], l[3][1])
        newCurves.append(curCurve)
        verts = r

# once we found all the segments above, make the remaining control points into curves (if any)
while (len(verts) > 3):
    curCurve = QtGui.QPainterPath()
    curCurve.moveTo(verts[0][0], verts[0][1])
    curCurve.cubicTo(verts[1][0], verts[1][1], verts[2][0], verts[2][1], verts[3][0], verts[3][1])
    newCurves.append(curCurve)
    verts = verts[3:]

代码中提到的“sliceBezier”是我在 *** 上找到的代码,用于在给定的 't' 值处将贝塞尔曲线一分为二,并返回新左侧的控制点(即小于 't')和右部分(即大于 't'):

def sliceBezier(points, t):
    if len(points) < 4:
        return points, []

    x1, y1 = points[0] 
    x2, y2 = points[1]
    x3, y3 = points[2]
    x4, y4 = points[3]

    x12 = (x2-x1)*t+x1
    y12 = (y2-y1)*t+y1

    x23 = (x3-x2)*t+x2
    y23 = (y3-y2)*t+y2

    x34 = (x4-x3)*t+x3
    y34 = (y4-y3)*t+y3

    x123 = (x23-x12)*t+x12
    y123 = (y23-y12)*t+y12

    x234 = (x34-x23)*t+x23
    y234 = (y34-y23)*t+y23

    x1234 = (x234-x123)*t+x123
    y1234 = (y234-y123)*t+y123

    return [(x1, y1), (x12, y12), (x123, y123), (x1234, y1234)], [(x1234,y1234),(x234,y234),(x34,y34),(x4,y4)]

...然后渲染它,我只是循环遍历新的曲线段集,就像我在原始帖子中对单个曲线所做的那样。

它不适用于每条曲线,因此它不是一个完美的解决方案,但至少目前它可能是可行的。

【讨论】:

以上是关于有没有一种简单的方法可以连接两条重叠的贝塞尔曲线的切线?的主要内容,如果未能解决你的问题,请参考以下文章

使用贝塞尔曲线进行插值 一种非常简单的平滑多边形的方法

Android 贝塞尔曲线实现QQ拖拽清除效果

贝塞尔曲线

怎么用函数画出弧线?

浅谈属性动画简单使用之实现爱的贝塞尔曲线浪漫告白效果

iOS 路径如下:将坐标列表转换为贝塞尔曲线