From bb355ba4424d077d404aafbb59b05776b2e88fa7 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Mon, 5 Sep 2016 19:50:44 +1000 Subject: [PATCH] vector: add CubeTo. Change-Id: Ia64b5b077172303981538de99cc14c5bcc99a3e4 Reviewed-on: https://go-review.googlesource.com/28489 Reviewed-by: David Crawshaw --- vector/vector.go | 66 ++++++++++++++++++++++++++++++++----------- vector/vector_test.go | 43 +++++++++++++++++++--------- 2 files changed, 79 insertions(+), 30 deletions(-) diff --git a/vector/vector.go b/vector/vector.go index 3723a84..218e764 100644 --- a/vector/vector.go +++ b/vector/vector.go @@ -134,35 +134,67 @@ func (z *Rasterizer) LineTo(b f32.Vec2) { // // The coordinates are allowed to be out of the Rasterizer's bounds. func (z *Rasterizer) QuadTo(b, c f32.Vec2) { - // We make a linear approximation to the curve. - // http://lists.nongnu.org/archive/html/freetype-devel/2016-08/msg00080.html - // gives the rationale for this evenly spaced heuristic instead of a - // recursive de Casteljau approach: - // - // The reason for the subdivision by n is that I expect the "flatness" - // computation to be semi-expensive (it's done once rather than on each - // potential subdivision) and also because you'll often get fewer - // subdivisions. Taking a circular arc as a simplifying assumption (ie a - // spherical cow), where I get n, a recursive approach would get 2^⌈lg n⌉, - // which, if I haven't made any horrible mistakes, is expected to be 33% - // more in the limit. a := z.pen - devx := a[0] - 2*b[0] + c[0] - devy := a[1] - 2*b[1] + c[1] - devsq := devx*devx + devy*devy + devsq := devSquared(a, b, c) if devsq >= 0.333 { const tol = 3 n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq)))) t, nInv := float32(0), 1/float32(n) for i := 0; i < n-1; i++ { t += nInv - z.LineTo(lerp(t, lerp(t, a, b), lerp(t, b, c))) + ab := lerp(t, a, b) + bc := lerp(t, b, c) + z.LineTo(lerp(t, ab, bc)) } } z.LineTo(c) } -// TODO: CubeTo for cubic Béziers. +// CubeTo adds a cubic Bézier segment, from the pen via b and c to d, and moves +// the pen to d. +// +// The coordinates are allowed to be out of the Rasterizer's bounds. +func (z *Rasterizer) CubeTo(b, c, d f32.Vec2) { + a := z.pen + devsq := devSquared(a, b, d) + if devsqAlt := devSquared(a, c, d); devsq < devsqAlt { + devsq = devsqAlt + } + if devsq >= 0.333 { + const tol = 3 + n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq)))) + t, nInv := float32(0), 1/float32(n) + for i := 0; i < n-1; i++ { + t += nInv + ab := lerp(t, a, b) + bc := lerp(t, b, c) + cd := lerp(t, c, d) + abc := lerp(t, ab, bc) + bcd := lerp(t, bc, cd) + z.LineTo(lerp(t, abc, bcd)) + } + } + z.LineTo(d) +} + +// devSquared returns a measure of how curvy the sequnce a to b to c is. It +// determines how many line segments will approximate a Bézier curve segment. +// +// http://lists.nongnu.org/archive/html/freetype-devel/2016-08/msg00080.html +// gives the rationale for this evenly spaced heuristic instead of a recursive +// de Casteljau approach: +// +// The reason for the subdivision by n is that I expect the "flatness" +// computation to be semi-expensive (it's done once rather than on each +// potential subdivision) and also because you'll often get fewer subdivisions. +// Taking a circular arc as a simplifying assumption (ie a spherical cow), +// where I get n, a recursive approach would get 2^⌈lg n⌉, which, if I haven't +// made any horrible mistakes, is expected to be 33% more in the limit. +func devSquared(a, b, c f32.Vec2) float32 { + devx := a[0] - 2*b[0] + c[0] + devy := a[1] - 2*b[1] + c[1] + return devx*devx + devy*devy +} // Draw implements the Drawer interface from the standard library's image/draw // package. diff --git a/vector/vector_test.go b/vector/vector_test.go index 9b8b642..3aa8c19 100644 --- a/vector/vector_test.go +++ b/vector/vector_test.go @@ -9,35 +9,52 @@ package vector import ( "image" "image/draw" + "image/png" + "os" "testing" "golang.org/x/image/math/f32" ) +// encodePNG is useful for manually debugging the tests. +func encodePNG(dstFilename string, src image.Image) error { + f, err := os.Create(dstFilename) + if err != nil { + return err + } + encErr := png.Encode(f, src) + closeErr := f.Close() + if encErr != nil { + return encErr + } + return closeErr +} + func TestBasicPath(t *testing.T) { want := []byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xd4, 0xdd, 0xc5, 0xab, 0x63, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xb3, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5b, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x20, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb3, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x63, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xab, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc5, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdd, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xaa, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x5f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x14, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x4a, 0x00, 0x00, + 0x00, 0x00, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x00, 0x00, + 0x00, 0x00, 0x66, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xe4, 0xff, 0xff, 0xff, 0xb6, 0x00, 0x00, + 0x00, 0x00, 0x0c, 0xf2, 0xff, 0xff, 0xfe, 0x9e, 0x15, 0x00, 0x15, 0x96, 0xff, 0xce, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x88, 0xfc, 0xe3, 0x43, 0x00, 0x00, 0x00, 0x00, 0x06, 0xcd, 0xdc, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xde, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } z := NewRasterizer(16, 16) z.MoveTo(f32.Vec2{2, 2}) + z.LineTo(f32.Vec2{8, 2}) z.QuadTo(f32.Vec2{14, 2}, f32.Vec2{14, 14}) - z.LineTo(f32.Vec2{5, 14}) + z.CubeTo(f32.Vec2{8, 2}, f32.Vec2{5, 20}, f32.Vec2{2, 8}) z.ClosePath() dst := image.NewAlpha(z.Bounds())