vector: add CubeTo.
Change-Id: Ia64b5b077172303981538de99cc14c5bcc99a3e4 Reviewed-on: https://go-review.googlesource.com/28489 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
714f2e47f7
commit
bb355ba442
|
@ -134,35 +134,67 @@ func (z *Rasterizer) LineTo(b f32.Vec2) {
|
||||||
//
|
//
|
||||||
// The coordinates are allowed to be out of the Rasterizer's bounds.
|
// The coordinates are allowed to be out of the Rasterizer's bounds.
|
||||||
func (z *Rasterizer) QuadTo(b, c f32.Vec2) {
|
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
|
a := z.pen
|
||||||
devx := a[0] - 2*b[0] + c[0]
|
devsq := devSquared(a, b, c)
|
||||||
devy := a[1] - 2*b[1] + c[1]
|
|
||||||
devsq := devx*devx + devy*devy
|
|
||||||
if devsq >= 0.333 {
|
if devsq >= 0.333 {
|
||||||
const tol = 3
|
const tol = 3
|
||||||
n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq))))
|
n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq))))
|
||||||
t, nInv := float32(0), 1/float32(n)
|
t, nInv := float32(0), 1/float32(n)
|
||||||
for i := 0; i < n-1; i++ {
|
for i := 0; i < n-1; i++ {
|
||||||
t += nInv
|
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)
|
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
|
// Draw implements the Drawer interface from the standard library's image/draw
|
||||||
// package.
|
// package.
|
||||||
|
|
|
@ -9,35 +9,52 @@ package vector
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/image/math/f32"
|
"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) {
|
func TestBasicPath(t *testing.T) {
|
||||||
want := []byte{
|
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, 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, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xaa, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xb3, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x5f, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x60, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x24, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5b, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x20, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x14, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb3, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x4a, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x12, 0x00, 0x00,
|
0x00, 0x00, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x63, 0x00, 0x00,
|
0x00, 0x00, 0x66, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xe4, 0xff, 0xff, 0xff, 0xb6, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xab, 0x00, 0x00,
|
0x00, 0x00, 0x0c, 0xf2, 0xff, 0xff, 0xfe, 0x9e, 0x15, 0x00, 0x15, 0x96, 0xff, 0xce, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc5, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x88, 0xfc, 0xe3, 0x43, 0x00, 0x00, 0x00, 0x00, 0x06, 0xcd, 0xdc, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdd, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xde, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf4, 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,
|
||||||
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 := NewRasterizer(16, 16)
|
||||||
z.MoveTo(f32.Vec2{2, 2})
|
z.MoveTo(f32.Vec2{2, 2})
|
||||||
|
z.LineTo(f32.Vec2{8, 2})
|
||||||
z.QuadTo(f32.Vec2{14, 2}, f32.Vec2{14, 14})
|
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()
|
z.ClosePath()
|
||||||
|
|
||||||
dst := image.NewAlpha(z.Bounds())
|
dst := image.NewAlpha(z.Bounds())
|
||||||
|
|
Loading…
Reference in New Issue
Block a user