vector: fix overflow when rasterizing a 30 degree line.

There are some obvious TODOs, but they will be follow-up commits.

This is about correctness, not performance, but for the record:

name                              old time/op  new time/op   delta
GlyphAlpha16Over-8                3.16µs ± 0%   3.38µs ± 0%   +6.96%         (p=0.000 n=9+10)
GlyphAlpha16Src-8                 3.06µs ± 0%   3.28µs ± 0%   +7.21%        (p=0.000 n=10+10)
GlyphAlpha32Over-8                4.92µs ± 1%   5.23µs ± 1%   +6.24%        (p=0.000 n=10+10)
GlyphAlpha32Src-8                 4.53µs ± 1%   4.83µs ± 0%   +6.69%        (p=0.000 n=10+10)
GlyphAlpha64Over-8                9.60µs ± 1%  10.21µs ± 0%   +6.36%        (p=0.000 n=10+10)
GlyphAlpha64Src-8                 8.04µs ± 0%   8.68µs ± 1%   +7.99%         (p=0.000 n=9+10)
GlyphAlpha128Over-8               23.1µs ± 0%   24.2µs ± 1%   +5.08%         (p=0.000 n=9+10)
GlyphAlpha128Src-8                16.8µs ± 0%   18.0µs ± 1%   +6.76%        (p=0.000 n=10+10)
GlyphAlpha256Over-8               68.6µs ± 1%   70.3µs ± 0%   +2.50%        (p=0.000 n=10+10)
GlyphAlpha256Src-8                43.6µs ± 0%   45.4µs ± 0%   +4.08%         (p=0.000 n=10+8)
GlyphRGBA16Over-8                 4.92µs ± 0%   5.14µs ± 0%   +4.48%          (p=0.000 n=9+9)
GlyphRGBA16Src-8                  4.39µs ± 0%   4.59µs ± 0%   +4.60%          (p=0.000 n=8+9)
GlyphRGBA32Over-8                 11.8µs ± 0%   12.2µs ± 1%   +2.89%         (p=0.000 n=9+10)
GlyphRGBA32Src-8                  9.79µs ± 1%  10.03µs ± 0%   +2.49%         (p=0.000 n=10+7)
GlyphRGBA64Over-8                 36.7µs ± 1%   37.5µs ± 1%   +2.23%        (p=0.000 n=10+10)
GlyphRGBA64Src-8                  28.5µs ± 0%   29.1µs ± 0%   +2.09%        (p=0.000 n=10+10)
GlyphRGBA128Over-8                 133µs ± 0%    135µs ± 0%   +1.51%         (p=0.000 n=10+9)
GlyphRGBA128Src-8                 99.1µs ± 0%  100.5µs ± 1%   +1.47%         (p=0.000 n=9+10)
GlyphRGBA256Over-8                 505µs ± 0%    511µs ± 0%   +1.18%         (p=0.000 n=9+10)
GlyphRGBA256Src-8                  372µs ± 0%    374µs ± 0%   +0.69%         (p=0.000 n=9+10)

Change-Id: Ice1d77de5bc2649f8cd88366bcae3c00e78d65c2
Reviewed-on: https://go-review.googlesource.com/31113
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/vector/raster_fixed.go b/vector/raster_fixed.go
index 7a8ea36..8a433c5 100644
--- a/vector/raster_fixed.go
+++ b/vector/raster_fixed.go
@@ -19,6 +19,10 @@
 	//
 	// When changing this number, also change the assembly code (search for ϕ
 	// in the .s files).
+	//
+	// TODO: drop ϕ from 10 to 9, so that ±1<<(3*ϕ+2) doesn't overflow an int32
+	// and we can therefore use int32 math instead of the slower int64 math in
+	// Rasterizer.fixedLineTo below.
 	ϕ = 10
 
 	fxOne          int1ϕ = 1 << ϕ
@@ -93,7 +97,7 @@
 			continue
 		}
 		buf := z.bufU32[y*width:]
-		d := dy * dir
+		d := dy * dir // d ranges up to ±1<<(1*ϕ).
 		x0, x1 := x, xNext
 		if x > xNext {
 			x0, x1 = x1, x0
@@ -131,8 +135,8 @@
 
 			if i := clamp(x0i, width); i < uint(len(buf)) {
 				// In ideal math: buf[i] += uint32(d * a0)
-				D := oneMinusX0fSquared
-				D *= d
+				D := oneMinusX0fSquared // D ranges up to ±1<<(1*ϕ).
+				D *= d                  // D ranges up to ±1<<(2*ϕ).
 				D /= twoOverS
 				buf[i] += uint32(D)
 			}
@@ -140,9 +144,15 @@
 			if x1i == x0i+2 {
 				if i := clamp(x0i+1, width); i < uint(len(buf)) {
 					// In ideal math: buf[i] += uint32(d * (fxOne - a0 - am))
-					D := twoOverS<<ϕ - oneMinusX0fSquared - x1fSquared
-					D *= d
-					D /= twoOverS
+					//
+					// (x1i == x0i+2) and (twoOverS == 2 * (x1 - x0)) implies
+					// that int64(twoOverS) ranges up to +1<<(1*ϕ+2).
+					//
+					// Convert to int64 to avoid overflow. Without that,
+					// TestRasterize30Degrees fails.
+					D := int64(twoOverS<<ϕ - oneMinusX0fSquared - x1fSquared) // D ranges up to ±1<<(2*ϕ+2).
+					D *= int64(d)                                             // D ranges up to ±1<<(3*ϕ+2).
+					D /= int64(twoOverS)
 					buf[i] += uint32(D)
 				}
 			} else {
@@ -151,12 +161,44 @@
 				// a1 := ((fxOneAndAHalf - x0f) << ϕ) / oneOverS
 
 				if i := clamp(x0i+1, width); i < uint(len(buf)) {
-					// In ideal math: buf[i] += uint32(d * (a1 - a0))
+					// In ideal math:
+					//	buf[i] += uint32(d * (a1 - a0))
+					// or equivalently (but better in non-ideal, integer math,
+					// with respect to rounding errors),
+					//	buf[i] += uint32(A * d / twoOverS)
+					// where
+					//	A = (a1 - a0) * twoOverS
+					//	  = a1*twoOverS - a0*twoOverS
+					// Noting that twoOverS/oneOverS equals 2, substituting for
+					// a0 and then a1, given above, yields:
+					//	A = a1*twoOverS - oneMinusX0fSquared
+					//	  = (fxOneAndAHalf-x0f)<<(ϕ+1) - oneMinusX0fSquared
+					//	  = fxOneAndAHalf<<(ϕ+1) - x0f<<(ϕ+1) - oneMinusX0fSquared
+					//
+					// This is a positive number minus two non-negative
+					// numbers. For an upper bound on A, the positive number is
+					//	P = fxOneAndAHalf<<(ϕ+1)
+					//	  < (2*fxOne)<<(ϕ+1)
+					//	  = fxOne<<(ϕ+2)
+					//	  = 1<<(2*ϕ+2)
+					//
+					// For a lower bound on A, the two non-negative numbers are
+					//	N = x0f<<(ϕ+1) + oneMinusX0fSquared
+					//	  ≤ x0f<<(ϕ+1) + fxOne*fxOne
+					//	  = x0f<<(ϕ+1) + 1<<(2*ϕ)
+					//	  < x0f<<(ϕ+1) + 1<<(2*ϕ+1)
+					//	  ≤ fxOne<<(ϕ+1) + 1<<(2*ϕ+1)
+					//	  = 1<<(2*ϕ+1) + 1<<(2*ϕ+1)
+					//	  = 1<<(2*ϕ+2)
+					//
+					// Thus, A ranges up to ±1<<(2*ϕ+2). It is possible to
+					// derive a tighter bound, but this bound is sufficient to
+					// reason about overflow.
 					//
 					// Convert to int64 to avoid overflow. Without that,
 					// TestRasterizePolygon fails.
-					D := int64((fxOneAndAHalf-x0f)<<(ϕ+1) - oneMinusX0fSquared)
-					D *= int64(d)
+					D := int64((fxOneAndAHalf-x0f)<<(ϕ+1) - oneMinusX0fSquared) // D ranges up to ±1<<(2*ϕ+2).
+					D *= int64(d)                                               // D ranges up to ±1<<(3*ϕ+2).
 					D /= int64(twoOverS)
 					buf[i] += uint32(D)
 				}
@@ -172,7 +214,32 @@
 				// a2 := a1 + (int1ϕ(x1i-x0i-3)<<(2*ϕ))/oneOverS
 
 				if i := clamp(x1i-1, width); i < uint(len(buf)) {
-					// In ideal math: buf[i] += uint32(d * (fxOne - a2 - am))
+					// In ideal math:
+					//	buf[i] += uint32(d * (fxOne - a2 - am))
+					// or equivalently (but better in non-ideal, integer math,
+					// with respect to rounding errors),
+					//	buf[i] += uint32(A * d / twoOverS)
+					// where
+					//	A = (fxOne - a2 - am) * twoOverS
+					//	  = twoOverS<<ϕ - a2*twoOverS - am*twoOverS
+					// Noting that twoOverS/oneOverS equals 2, substituting for
+					// am and then a2, given above, yields:
+					//	A = twoOverS<<ϕ - a2*twoOverS - x1f*x1f
+					//	  = twoOverS<<ϕ - a1*twoOverS - (int1ϕ(x1i-x0i-3)<<(2*ϕ))*2 - x1f*x1f
+					//	  = twoOverS<<ϕ - a1*twoOverS - int1ϕ(x1i-x0i-3)<<(2*ϕ+1) - x1f*x1f
+					// Substituting for a1, given above, yields:
+					//	A = twoOverS<<ϕ - ((fxOneAndAHalf - x0f)<<ϕ)*2 - int1ϕ(x1i-x0i-3)<<(2*ϕ+1) - x1f*x1f
+					//	  = twoOverS<<ϕ - (fxOneAndAHalf - x0f)<<(ϕ+1) - int1ϕ(x1i-x0i-3)<<(2*ϕ+1) - x1f*x1f
+					//
+					// TODO: re-factor that equation some more: twoOverS equals
+					// 2*(x1-x0), so a substantial part of twoOverS<<ϕ and
+					// int1ϕ(x1i-x0i-3)<<(2*ϕ+1) should cancel each other out.
+					// Doing subtract-then-shift instead of shift-then-subtract
+					// could mean that we can use the faster int32 math,
+					// instead of int64, but still avoid overflow:
+					//	A = B<<ϕ - x1f*x1f
+					// where
+					//	B = twoOverS - (fxOneAndAHalf - x0f)<<1 - int1ϕ(x1i-x0i-3)<<(ϕ+1)
 					//
 					// Convert to int64 to avoid overflow. Without that,
 					// TestRasterizePolygon fails.
@@ -188,8 +255,8 @@
 
 			if i := clamp(x1i, width); i < uint(len(buf)) {
 				// In ideal math: buf[i] += uint32(d * am)
-				D := x1fSquared
-				D *= d
+				D := x1fSquared // D ranges up to ±1<<(2*ϕ).
+				D *= d          // D ranges up to ±1<<(3*ϕ).
 				D /= twoOverS
 				buf[i] += uint32(D)
 			}
diff --git a/vector/vector.go b/vector/vector.go
index 9caccf8..8538bb9 100644
--- a/vector/vector.go
+++ b/vector/vector.go
@@ -30,7 +30,7 @@
 	"golang.org/x/image/math/f32"
 )
 
-// floatingPointMathThreshold is the width or hight above which the rasterizer
+// floatingPointMathThreshold is the width or height above which the rasterizer
 // chooses to used floating point math instead of fixed point math.
 //
 // Both implementations of line segmentation rasterization (see raster_fixed.go
diff --git a/vector/vector_test.go b/vector/vector_test.go
index 6e8a1d1..e2cbbdb 100644
--- a/vector/vector_test.go
+++ b/vector/vector_test.go
@@ -13,6 +13,7 @@
 	"image/draw"
 	"image/png"
 	"math"
+	"math/rand"
 	"os"
 	"path/filepath"
 	"testing"
@@ -146,9 +147,60 @@
 	}
 }
 
+func TestRasterize30Degrees(t *testing.T) {
+	z := NewRasterizer(8, 8)
+	z.MoveTo(f32.Vec2{4, 4})
+	z.LineTo(f32.Vec2{8, 4})
+	z.LineTo(f32.Vec2{4, 6})
+	z.ClosePath()
+
+	dst := image.NewAlpha(z.Bounds())
+	z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
+
+	if err := checkCornersCenter(dst); err != nil {
+		t.Error(err)
+	}
+}
+
+func TestRasterizeRandomLineTos(t *testing.T) {
+	var z Rasterizer
+	for i := 5; i < 50; i++ {
+		n, rng := 0, rand.New(rand.NewSource(int64(i)))
+
+		z.Reset(i+2, i+2)
+		z.MoveTo(f32.Vec2{float32(i / 2), float32(i / 2)})
+		for ; rng.Intn(16) != 0; n++ {
+			x := 1 + rng.Intn(i)
+			y := 1 + rng.Intn(i)
+			z.LineTo(f32.Vec2{float32(x), float32(y)})
+		}
+		z.ClosePath()
+
+		dst := image.NewAlpha(z.Bounds())
+		z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
+
+		if err := checkCorners(dst); err != nil {
+			t.Errorf("i=%d (%d nodes): %v", i, n, err)
+		}
+	}
+}
+
 // checkCornersCenter checks that the corners of the image are all 0x00 and the
 // center is 0xff.
 func checkCornersCenter(m *image.Alpha) error {
+	if err := checkCorners(m); err != nil {
+		return err
+	}
+	size := m.Bounds().Size()
+	center := m.Pix[(size.Y/2)*m.Stride+(size.X/2)]
+	if center != 0xff {
+		return fmt.Errorf("center: got %#02x, want 0xff", center)
+	}
+	return nil
+}
+
+// checkCorners checks that the corners of the image are all 0x00.
+func checkCorners(m *image.Alpha) error {
 	size := m.Bounds().Size()
 	corners := [4]uint8{
 		m.Pix[(0*size.Y+0)*m.Stride+(0*size.X+0)],
@@ -159,10 +211,6 @@
 	if corners != [4]uint8{} {
 		return fmt.Errorf("corners were not all zero: %v", corners)
 	}
-	center := m.Pix[(size.Y/2)*m.Stride+(size.X/2)]
-	if center != 0xff {
-		return fmt.Errorf("center: got %#02x, want 0xff", center)
-	}
 	return nil
 }