-
Notifications
You must be signed in to change notification settings - Fork 0
/
bezier.go
127 lines (108 loc) · 3.46 KB
/
bezier.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package bezier
import (
"fmt"
"image"
)
// The "F" suffix on the struct names means float64.
type PointF struct {
X, Y float64
}
type BezierF struct {
P0, P1, P2, P3 PointF
}
type Bezier struct {
P0, P1, P2, P3 image.Point
}
func SvgControlPointsI(p []image.Point) []string {
return ControlPointsToSvgI(GetControlPointsI(p))
}
func ControlPointsToSvgI(beziers []Bezier) []string {
svgs := make([]string, len(beziers))
for j, b := range(beziers) {
svgs[j] = "M " + pointAsKnot(b.P0) + " C " + pointAsKnot(b.P1) + ", " +
pointAsKnot(b.P2) + ", " + pointAsKnot(b.P3)
}
return svgs
}
// Converts an image.Point to a string consisting of the x value and
// the y value, separated by a space, suitable for SVG paths.
func pointAsKnot(p image.Point) string {
return fmt.Sprintf("%d %d", p.X, p.Y)
}
func GetControlPointsI(p []image.Point) []Bezier {
// Convert integer values to floats, calcalute control points, and then
// convert back to integers.
pf := make([]PointF, len(p))
for j, source := range(p) {
pf[j].X = float64(source.X)
pf[j].Y = float64(source.Y)
}
bezierF := GetControlPointsF(pf)
bezier := make([]Bezier, len(bezierF))
for j, bz := range(bezierF) {
bezier[j].P0.X = int(bz.P0.X)
bezier[j].P0.Y = int(bz.P0.Y)
bezier[j].P1.X = int(bz.P1.X)
bezier[j].P1.Y = int(bz.P1.Y)
bezier[j].P2.X = int(bz.P2.X)
bezier[j].P2.Y = int(bz.P2.Y)
bezier[j].P3.X = int(bz.P3.X)
bezier[j].P3.Y = int(bz.P3.Y)
}
return bezier
}
// Algorithm ported from https://www.particleincell.com/wp-content/uploads/2012/06/bezier-spline.js
func GetControlPointsF(p []PointF) []BezierF {
n := len(p) - 1 // number of segments
bezier := make([]BezierF, n)
// Fill in the end points of the Bezier curves.
for j, _ := range(bezier) {
bezier[j].P0.X, bezier[j].P0.Y = p[j].X, p[j].Y
bezier[j].P3.X, bezier[j].P3.Y = p[j+1].X, p[j+1].Y
}
// Slices needed for calculations, listed as "rhs vector" on page listed above.
a := make([]PointF, n)
b := make([]PointF, n)
c := make([]PointF, n)
r := make([]PointF, n)
a[0].X, a[0].Y = 0.0, 0.0
b[0].X, b[0].Y = 2.0, 2.0
c[0].X, c[0].Y = 1.0, 1.0
r[0].X = p[0].X + 2.0 * p[1].X
r[0].Y = p[0].Y + 2.0 * p[1].Y
for j := 1; j < (n-1); j++ {
a[j].X, a[j].Y = 1.0, 1.0
b[j].X, b[j].Y = 4.0, 4.0
c[j].X, c[j].Y = 1.0, 1.0
r[j].X = 4.0 * p[j].X + 2.0 * p[j+1].X
r[j].Y = 4.0 * p[j].Y + 2.0 * p[j+1].Y
}
a[n-1].X, a[n-1].Y = 2.0, 2.0
b[n-1].X, b[n-1].Y = 7.0, 7.0
c[n-1].X, c[n-1].Y = 0.0, 0.0
r[n-1].X = 8.0 * p[n-1].X + p[n].X
r[n-1].Y = 8.0 * p[n-1].Y + p[n].Y
for j := 1; j < n; j++ {
m := a[j].X / b[j-1].X
b[j].X = b[j].X - m * c[j-1].X
r[j].X = r[j].X - m * r[j-1].X
m = a[j].Y / b[j-1].Y
b[j].Y = b[j].Y - m * c[j-1].Y
r[j].Y = r[j].Y - m * r[j-1].Y
}
// Compute first control point (P1 in BeziefF), from last to first.
bezier[n-1].P1.X = r[n-1].X / b[n-1].X
bezier[n-1].P1.Y = r[n-1].Y / b[n-1].Y
for j := n - 2; j >= 0; j-- {
bezier[j].P1.X = (r[j].X - c[j].X * bezier[j+1].P1.X) / b[j].X
bezier[j].P1.Y = (r[j].Y - c[j].Y * bezier[j+1].P1.Y) / b[j].Y
}
// Compute second control point (P2 in BezierF), from first to last.
for j := 0; j < n - 1; j++ {
bezier[j].P2.X = 2 * p[j+1].X - bezier[j+1].P1.X
bezier[j].P2.Y = 2 * p[j+1].Y - bezier[j+1].P1.Y
}
bezier[n-1].P2.X = 0.5 * (p[n].X + bezier[n-1].P1.X)
bezier[n-1].P2.Y = 0.5 * (p[n].Y + bezier[n-1].P1.Y)
return bezier
}