2011-07-08 22 views
13

Estoy buscando una solución para convertir una ruta SVG a mano alzada, dibujada por el usuario, que consta de porciones en segmentos de línea a segmentos, en una más suave.¿Cómo suavizar una ruta SVG dibujada a mano alzada?

El idioma preferido es JavaScript, pero cualquier consejo es bienvenido.

+0

Intenta usar curvas http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands – Gerben

Respuesta

29

Antes que nada, recomendaría usar una buena biblioteca de gráficos, como raphael. Simplificará el proceso de usar javascript para realizar el dibujo.

Un método muy simple de suavizar es convertir todos los comandos de línea a comandos con una curva equivalente a comandos y calcular algunos puntos de control según los ángulos de cada segmento de línea. Por ejemplo,

<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> 

convierte

<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> 

Ambos deben dibujar un triángulo equilátero

El siguiente paso sería calcular la posición de los puntos de control. En general, querrás que los puntos de control a cada lado de una esquina suave caigan sobre una línea imaginaria que pasa por el vértice. En el caso del punto superior del triángulo equilátero, esta sería una línea horizontal. Después de algunas manipulaciones, se puede obtener algo como esto:

<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> 

La parte difícil es el cálculo de los puntos de control, pero que se convierte en no mucho más que un simple problema de trigonometría. Como mencioné anteriormente, el objetivo aquí es poner los dos puntos de control en una línea que biseca el vértice de la esquina. Por ejemplo, supongamos que tenemos dos segmentos de línea:

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 

conociendo el ángulo tangente, podemos calcular el punto de control posiciona

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) 

El último problema es donde poner cada punto de control en el curveTo real comando:

CX1 Y1 X2 Y2 X3 Y3 

X3 y Y3 definen la ubicación del vértice. X1 Y1 y X2 Y2 definen los puntos de control. Puede pensar que X1 Y1 define el vector de cómo ingresar el vértice y X2 Y2 define el vector de cómo salir. Ahora que tiene los dos puntos de control que debe decidir sobre

CXcp1 Ycp1 Xcp2 Ycp2 0 0 

o

CXcp2 Ycp2 Xcp1 Ycp1 0 0 

esta es una decisión importante. Si los haces hacia atrás, la forma se verá como un bucle. En este punto, debe poder determinar cómo debe tomarse esta decisión ...

De nuevo, esta es una solución muy simple, pero tiende a verse bien para las rutas dibujadas a mano. Una solución mejor podría llevarlo un paso más allá y mover el punto de intersección hacia adentro hacia la sección cóncava de cada intersección de segmento de línea. Esto es un poco más desafiante

+1

¿Puedes dar un ejemplo de cómo calcular los puntos de control? – florianguenther

+0

Agregué un poco de matemática que debería ayudarte en este camino – jordancpaul

+0

Esta es probablemente una de las mejores respuestas que he visto +10 – austinbv

2

Estoy con el mismo problema, mirando a paperjs ejemplos vi que tienen un ejemplo de path simplification, acechando el algoritmo detrás de él se puede ver aquí: https://github.com/paperjs/paper.js/blob/master/src/path/PathFitter.js

Es el algoritmo que simplifican la bruja ruta es una versión js (con optimizaciones) de un estudio académico llamado "An algorithm for automatically fitting digitized curves".

Estoy trabajando en la extracción de este algoritmo solamente y probablemente lo publique como un complemento a svg.js.

+0

¿Has conseguido implementarla en svg.js? – Rohit

+0

lo siento, no :( No estoy trabajando en este proyecto paralelo por ahora. – madcampos

1

Imaginemos el dibujo de usuario es un conjunto de tuplas, podríamos hacer algo como

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>

explicaciones detalladas en this article.

Cuestiones relacionadas