Как сгладить нарисованный от руки путь SVG?

Я ищу решение для преобразования нарисованного пользователем пути SVG от руки, состоящего из множества сегментов auf LineTo, в более плавный.

Предпочтительным языком будет JavaScript, но любые советы приветствуются.


person florianguenther    schedule 08.07.2011    source источник


Ответы (3)


прежде всего, я бы рекомендовал использовать хорошую графическую библиотеку, например, raphael. Это упростит процесс фактического использования javascript для выполнения рисования.

Очень простой метод сглаживания заключается в преобразовании всех команд lineto в эквивалентные команды curveto и расчете некоторых контрольных точек на основе углов каждого сегмента линии. Например,

<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

or

CXcp2 Ycp2 Xcp1 Ycp1 0 0

это важное решение. Если вы перевернете их, форма будет похожа на петлю. К этому моменту вы должны быть в состоянии определить, как это решение должно быть принято...

Опять же, это очень простое решение, но оно хорошо выглядит для нарисованных от руки путей. Лучшее решение может сделать еще один шаг вперед и переместить точку пересечения внутрь к вогнутой части пересечения каждого линейного сегмента. Это немного сложнее.

person jordancpaul    schedule 08.07.2011
comment
не могли бы вы привести пример, как рассчитать контрольные точки? - person florianguenther; 11.07.2011
comment
Я добавил немного математики, которая должна помочь вам на этом пути. - person jordancpaul; 20.07.2011
comment
Это, наверное, один из лучших ответов, которые я видел +10 - person austinbv; 20.09.2011

Давайте представим, что пользовательский рисунок представляет собой массив кортежей, мы могли бы сделать что-то вроде

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)
<svg viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg" class="svg">
</svg>

Подробные пояснения в этой статье .

person François Romain    schedule 26.07.2017

у меня та же проблема, глядя на примеры paperjs, я увидел, что у них есть один пример для упрощения пути, скрывающийся за ним алгоритм, вы можете увидеть его здесь: https://github.com/paperjs/paper.js/blob/master/src/path/PathFitter.js

Алгоритм, упрощающий путь, представляет собой js-версию (с оптимизацией) академического исследования под названием "Алгоритм автоматической подгонки оцифрованных кривых".

Я работаю только над извлечением этого алгоритма и, вероятно, опубликую его как плагин к svg.js.

person madcampos    schedule 11.10.2013
comment
у вас получилось реализовать это в svg.js? - person Rohit; 13.01.2015
comment
Я знаю, что это многолетняя ветка, но я начал с плагина модификатора пути для svg.js, пока просто упрощение, но добавление кривой Безье также должно быть довольно простым. github.com/JanMisker/svg.pathmod.js - person Jan Misker; 24.04.2019