diff --git a/README.md b/README.md index ec9bb52..dd3c57c 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ DrawImageAnchored(im image.Image, x, y int, ax, ay float64) MoveTo(x, y float64) LineTo(x, y float64) QuadraticTo(x1, y1, x2, y2 float64) +CubicTo(x1, y1, x2, y2, x3, y3 float64) ClosePath() ClearPath() @@ -132,7 +133,6 @@ even better, implement it and submit a pull request! - Clipping Regions - Gradients / Patterns -- Cubic Beziers - Dashed Lines ## How Do it Do? diff --git a/bezier.go b/bezier.go new file mode 100644 index 0000000..1fcfd8c --- /dev/null +++ b/bezier.go @@ -0,0 +1,32 @@ +package gg + +import "math" + +func cubic(x0, y0, x1, y1, x2, y2, x3, y3, t float64) (x, y float64) { + u := 1 - t + a := u * u * u + b := 3 * u * u * t + c := 3 * u * t * t + d := t * t * t + x = a*x0 + b*x1 + c*x2 + d*x3 + y = a*y0 + b*y1 + c*y2 + d*y3 + return +} + +func CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3 float64) []Point { + l := (math.Hypot(x1-x0, y1-y0) + + math.Hypot(x2-x1, y2-y1) + + math.Hypot(x3-x2, y3-y2)) + n := int((l + 0.5)) + if n < 4 { + n = 4 + } + d := float64(n) - 1 + result := make([]Point, n) + for i := 0; i < n; i++ { + t := float64(i) / d + x, y := cubic(x0, y0, x1, y1, x2, y2, x3, y3, t) + result[i] = Point{x, y} + } + return result +} diff --git a/context.go b/context.go index 216c3a2..a66ebd9 100644 --- a/context.go +++ b/context.go @@ -9,7 +9,6 @@ import ( "github.com/golang/freetype/raster" "golang.org/x/image/font" "golang.org/x/image/font/basicfont" - "golang.org/x/image/math/fixed" ) type LineCap int @@ -41,7 +40,8 @@ type Context struct { color color.Color strokePath raster.Path fillPath raster.Path - start fixed.Point26_6 + start Point + current Point lineWidth float64 lineCap LineCap lineJoin LineJoin @@ -170,12 +170,14 @@ func (dc *Context) SetRGB(r, g, b float64) { func (dc *Context) MoveTo(x, y float64) { if len(dc.fillPath) > 0 { - dc.fillPath.Add1(dc.start) + dc.fillPath.Add1(dc.start.Fixed()) } x, y = dc.TransformPoint(x, y) - dc.start = fp(x, y) - dc.strokePath.Start(dc.start) - dc.fillPath.Start(dc.start) + p := Point{x, y} + dc.strokePath.Start(p.Fixed()) + dc.fillPath.Start(p.Fixed()) + dc.start = p + dc.current = p } func (dc *Context) LineTo(x, y float64) { @@ -183,29 +185,48 @@ func (dc *Context) LineTo(x, y float64) { dc.MoveTo(x, y) } else { x, y = dc.TransformPoint(x, y) - p := fp(x, y) - dc.strokePath.Add1(p) - dc.fillPath.Add1(p) + p := Point{x, y} + dc.strokePath.Add1(p.Fixed()) + dc.fillPath.Add1(p.Fixed()) + dc.current = p } } func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) { if len(dc.strokePath) == 0 { dc.MoveTo(x1, y1) - } else { - x1, y1 = dc.TransformPoint(x1, y1) - x2, y2 = dc.TransformPoint(x2, y2) - p1 := fp(x1, y1) - p2 := fp(x2, y2) - dc.strokePath.Add2(p1, p2) - dc.fillPath.Add2(p1, p2) + } + x1, y1 = dc.TransformPoint(x1, y1) + x2, y2 = dc.TransformPoint(x2, y2) + p1 := Point{x1, y1} + p2 := Point{x2, y2} + dc.strokePath.Add2(p1.Fixed(), p2.Fixed()) + dc.fillPath.Add2(p1.Fixed(), p2.Fixed()) + dc.current = p2 +} + +func (dc *Context) CubicTo(x1, y1, x2, y2, x3, y3 float64) { + if len(dc.strokePath) == 0 { + dc.MoveTo(x1, y1) + } + x0, y0 := dc.current.X, dc.current.Y + x1, y1 = dc.TransformPoint(x1, y1) + x2, y2 = dc.TransformPoint(x2, y2) + x3, y3 = dc.TransformPoint(x3, y3) + points := CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3) + for _, p := range points[1:] { + f := p.Fixed() + dc.strokePath.Add1(f) + dc.fillPath.Add1(f) + dc.current = p } } func (dc *Context) ClosePath() { if len(dc.strokePath) > 0 { - dc.strokePath.Add1(dc.start) - dc.fillPath.Add1(dc.start) + dc.strokePath.Add1(dc.start.Fixed()) + dc.fillPath.Add1(dc.start.Fixed()) + dc.current = dc.start } } @@ -257,7 +278,7 @@ func (dc *Context) FillPreserve() { if len(dc.fillPath) > 0 { path = make(raster.Path, len(dc.fillPath)) copy(path, dc.fillPath) - path.Add1(dc.start) + path.Add1(dc.start.Fixed()) } painter := raster.NewRGBAPainter(dc.im) painter.SetColor(dc.color) diff --git a/point.go b/point.go new file mode 100644 index 0000000..cdbc2c8 --- /dev/null +++ b/point.go @@ -0,0 +1,11 @@ +package gg + +import "golang.org/x/image/math/fixed" + +type Point struct { + X, Y float64 +} + +func (p Point) Fixed() fixed.Point26_6 { + return fp(p.X, p.Y) +}