如何平滑徒手绘制的SVG路径?

时间:2011-07-08 07:50:48

标签: javascript path svg smooth

我正在寻找一种解决方案,将徒手绘制的用户绘制的SVG路径转换为更流畅的SVF路径,包括许多auf LineTo段。

首选语言是JavaScript,但欢迎任何建议。

3 个答案:

答案 0 :(得分:29)

首先,我建议使用一个好的图形库,例如raphael。它将简化实际使用javascript执行绘图的过程。

一种非常简单的平滑方法是使用等效的curveto命令转换所有lineto命令,并根据每个线段的角度计算一些控制点。例如,

<svg width="1000" height="1000" version="1.1"
xmlns="http://www.w3.org/2000/svg">

<path d="
M250 150 
L150 350
L350 350
L250 150
" />

</svg> 

变为

<svg width="1000" height="1000" version="1.1"
xmlns="http://www.w3.org/2000/svg">

<path d="
M250 150 
C250 150 150 350 150 350
C150 350 350 350 350 350
C350 350 250 150 250 150
" />

</svg> 

这两个都应画一个等边三角形

下一步是计算控制点的位置。通常,您希望平滑角两侧的控制点落在穿过顶点的假想线上。在等边三角形的顶点的情况下,这将是水平线。经过一些操作,你可以得到这样的东西:

<svg width="1000" height="1000" version="1.1"
xmlns="http://www.w3.org/2000/svg">

<path d="
M250 150 
C230 150 140 333 150 350
C160 367 340 367 350 350
C360 333 270 150 250 150
" />

</svg> 

棘手的部分是计算控制点,但这不仅仅是一个简单的触发问题。正如我之前提到的,这里的目标是将两个控制点放在一条平分角点顶点的线上。例如,假设我们有两个线段:

A. (0,0) to (3,2)
B. (0,0) to (1,-4)

the absolute angle of A is arctan(2/3) = 33.69 deg
the absolute angle of B is arctan(-4/1) = -75.96 deg
the bisection angle of AB is (33.69 + -75.96)/2 = -21.135
the tangent angle is AB is (-21.135 + 90) = 68.865

知道切线角度,我们可以计算控制点位置

smoothness = radius = r
tangent angle = T
Vertex X = Xv
Vertex Y = Yv

Control Point 1:
Xcp1 = cos(T)*r
Ycp1 = sin(T)*r

Control Point 2:
Xcp2 = cos(T)*(-r)
Ycp2 = sin(T)*(-r)

最后一个问题是将每个控制点放在实际的curveTo命令中:

CX1 Y1 X2 Y2 X3 Y3

X3和Y3定义顶点位置。 X1 Y1和X2 Y2定义控制点。您可以将X1 Y1视为定义如何进入顶点的向量,将X2 Y2定义为定义如何离开的向量。现在您有两个控制点,您必须决定

CXcp1 Ycp1 Xcp2 Ycp2 0 0

CXcp2 Ycp2 Xcp1 Ycp1 0 0
这是一个重要的决定。如果你向后移动它们,形状将看起来像一个循环。到目前为止,你应该能够确定如何做出这个决定......

同样,这是一个非常简单的解决方案,但它往往看起来很适合手绘路径。一个更好的解决方案可能会更进一步,并将交叉点向内移向每个线段交叉点的凹面部分。这更具挑战性。

答案 1 :(得分:2)

我遇到同样的问题,看看paperjs的例子,我看到他们有path simplification的一个例子,潜伏在它背后的算法,你可以在这里看到它:https://github.com/paperjs/paper.js/blob/master/src/path/PathFitter.js

简化路径的算法是一个名为"An algorithm for automatically fitting digitized curves"的学术研究的js版本(带优化)。

我正在开发仅提取此算法的工作,并可能将其公开为svg.js的插件。

答案 2 :(得分:2)

让我们想象用户绘图是一个元组数组,我们可以做类似的事情

&#13;
&#13;
const points = [[100, 50], [50, 15], [5, 60], [10, 20], [20, 10], [30, 190], [40, 10], [50, 60], [60, 120], [70, 10], [80, 50], [90, 50], [120, 10], [150, 80], [160, 10] ]

const lineProperties = (pointA, pointB) => {
  const lengthX = pointB[0] - pointA[0]
  const lengthY = pointB[1] - pointA[1]
  return {
    length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
    angle: Math.atan2(lengthY, lengthX)
  }
}

const controlPointCalc = (current, previous, next, reverse) => {
  const c = current
  const p = previous ? previous : c
  const n = next ? next : c
  const smoothing = 0.2
  const o = lineProperties(p, n)
  const rev = reverse ? Math.PI : 0

  const x = c[0] + Math.cos(o.angle + rev) * o.length * smoothing
  const y = c[1] + Math.sin(o.angle + rev) * o.length * smoothing

  return [x, y]
}

const svgPathRender = points => {      
  const d = points.reduce((acc, e, i, a) => {
      if (i > 0) {
        const cs = controlPointCalc(a[i - 1], a[i - 2], e)
        const ce = controlPointCalc(e, a[i - 1], a[i + 1], true)
        return `${acc} C ${cs[0]},${cs[1]} ${ce[0]},${ce[1]} ${e[0]},${e[1]}`
      } else {
        return `${acc} M ${e[0]},${e[1]}`
      }
    },'')

  return `<path d="${d}" fill="none" stroke="black" />`
}

const svg = document.querySelector('.svg')

svg.innerHTML = svgPathRender(points)
&#13;
<svg viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" class="svg">
</svg>
&#13;
&#13;
&#13;

this article中的详细说明。