2011-07-08 35 views
13

Tôi đang tìm một giải pháp để chuyển đổi một đường dẫn SVG tự do, do người dùng rút ra, bao gồm nhiều phân đoạn LineTo auf, thành một đoạn mượt mà hơn.Làm thế nào để làm mịn đường dẫn SVG được vẽ tự do?

Ngôn ngữ ưa thích sẽ là JavaScript, nhưng mọi lời khuyên đều được chào đón.

+0

Hãy thử sử dụng các đường cong http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands – Gerben

Trả lời

29

trước hết, tôi khuyên bạn nên sử dụng thư viện đồ họa tốt, chẳng hạn như raphael. Nó sẽ đơn giản hóa quá trình thực sự sử dụng javascript để thực hiện bản vẽ.

Một phương pháp làm mịn rất đơn giản là chuyển đổi tất cả các lệnh lineto với lệnh curveto tương đương và tính toán một số điểm kiểm soát dựa trên các góc của mỗi đoạn đường. Ví dụ,

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

trở thành

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

Cả hai nên vẽ một tam giác đều

Bước tiếp theo sẽ là để tính toán vị trí của các điểm kiểm soát. Nói chung, bạn sẽ muốn các điểm điều khiển ở hai bên của một góc mịn để rơi trên một đường tưởng tượng đi qua đỉnh. Trong trường hợp điểm trên cùng của tam giác đều, đây sẽ là đường nằm ngang. Sau một số thao tác, bạn có thể nhận được một cái gì đó như thế này:

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

Phần khó khăn là tính toán các điểm kiểm soát, nhưng điều đó biến thành không nhiều hơn một vấn đề trig đơn giản. Như tôi đã đề cập ở trên, mục tiêu ở đây là đặt hai điểm điều khiển trên một đường thẳng chia đôi đỉnh góc. Ví dụ, giả sử chúng ta có hai đoạn thẳng:

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 

biết góc tiếp xúc, chúng tôi có thể tính toán điểm kiểm soát vị trí

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) 

Vấn đề cuối cùng là nơi để đặt mỗi điểm kiểm soát trong curveTo thực tế lệnh:

CX1 Y1 X2 Y2 X3 Y3 

X3 và Y3 xác định vị trí đỉnh. X1 Y1 và X2 Y2 xác định các điểm điều khiển. Bạn có thể nghĩ về X1 Y1 như xác định véc tơ về cách nhập đỉnh và X2 Y2 như xác định véc tơ về cách rời khỏi. Bây giờ bạn có hai điểm kiểm soát bạn phải quyết định trên

CXcp1 Ycp1 Xcp2 Ycp2 0 0 

hoặc

CXcp2 Ycp2 Xcp1 Ycp1 0 0 

đây là một quyết định quan trọng. Nếu bạn đưa chúng về phía sau, hình dạng sẽ trông giống như một vòng lặp. Đến thời điểm này, bạn sẽ có thể xác định cách đưa ra quyết định này ...

Một lần nữa, đây là giải pháp rất đơn giản, nhưng nó có vẻ tốt cho các đường vẽ tay. Một giải pháp tốt hơn có thể tiến thêm một bước nữa và di chuyển điểm giao nhau vào trong phần lõm của mỗi giao lộ đoạn đường. Điều này khá khó khăn hơn một chút.

+1

bạn có thể đưa ra ví dụ về cách tính điểm kiểm soát không? – florianguenther

+0

Tôi đã thêm một chút toán học sẽ giúp bạn xuống con đường này – jordancpaul

+0

Đây có lẽ là một trong những câu trả lời hay nhất mà tôi đã thấy +10 – austinbv

2

tôi với cùng một vấn đề, nhìn vào paperjs ví dụ tôi thấy rằng họ có một ví dụ cho path simplification, rình rập các thuật toán đằng sau nó, bạn có thể nhìn thấy nó ở đây: https://github.com/paperjs/paper.js/blob/master/src/path/PathFitter.js

Đó là thuật toán đơn giản hóa phù thủy đường là một phiên bản js (với tối ưu hóa) của một nghiên cứu học thuật có tên là "An algorithm for automatically fitting digitized curves".

Tôi đang thực hiện công việc trích xuất thuật toán này và có thể sẽ coi đó là một plugin cho svg.js.

+0

bạn có nhận được xung quanh để triển khai nó trong svg.js không? – Rohit

+0

xin lỗi, nope :( Tôi hiện không làm việc với dự án này. – madcampos

1

Hãy tưởng tượng bản vẽ sử dụng là một mảng của các bộ, chúng ta có thể làm điều gì đó như

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>

giải thích chi tiết trong this article.

Các vấn đề liên quan