Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

Прежде всего, я бы рекомендовал использовать хорошую графическую библиотеку, такую ​​как 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

или

CXcp2 Ycp2 Xcp1 Ycp1 0 0

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

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

Ответ 2

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

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

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

Ответ 3

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

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>