git.lucas.co / go_mono
git clone https://git.lucas.co/go_mono.git

commitcd8486aac9e9f06d9a2a9f1eab215ba0945029f0
parentbb355ba442
authorNigel Tao <nigeltao@golang.org>
date2016-09-21 22:15
vector: implement DrawOp == draw.Over.

Some "TODO: the general case" lines remain.

Change-Id: If66e484a00d5ea3fce9db37d4ee493739648daa3
Reviewed-on: https://go-review.googlesource.com/29495
Reviewed-by: David Crawshaw <crawshaw@golang.org>

 vector/raster_floating.go | 31 +++++++++++++++++++++++--
 vector/vector.go          | 21 ++++++++++++++---
 vector/vector_test.go     | 59 +++++++++++++++++++++++++++++++----------------
 3 files changed, 86 insertions(+), 25 deletions(-)

diff --git a/vector/raster_floating.go b/vector/raster_floating.go
index d03936a..8171edc 100644
--- a/vector/raster_floating.go
+++ b/vector/raster_floating.go
@@ -122,7 +122,7 @@ func (z *Rasterizer) floatingLineTo(b f32.Vec2) {
 	}
 }
 
-func floatingAccumulate(dst []uint8, src []float32) {
+const (
 	// almost256 scales a floating point value in the range [0, 1] to a uint8
 	// value in the range [0x00, 0xff].
 	//
@@ -136,8 +136,16 @@ func floatingAccumulate(dst []uint8, src []float32) {
 	// instead of the maximal value 0xff.
 	//
 	// math.Float32bits(almost256) is 0x437fffff.
-	const almost256 = 255.99998
+	almost256 = 255.99998
 
+	// almost65536 scales a floating point value in the range [0, 1] to a
+	// uint16 value in the range [0x0000, 0xffff].
+	//
+	// math.Float32bits(almost65536) is 0x477fffff.
+	almost65536 = almost256 * 256
+)
+
+func floatingAccumulateOpSrc(dst []uint8, src []float32) {
 	acc := float32(0)
 	for i, v := range src {
 		acc += v
@@ -151,3 +159,22 @@ func floatingAccumulate(dst []uint8, src []float32) {
 		dst[i] = uint8(almost256 * a)
 	}
 }
+
+func floatingAccumulateOpOver(dst []uint8, src []float32) {
+	acc := float32(0)
+	for i, v := range src {
+		acc += v
+		a := acc
+		if a < 0 {
+			a = -a
+		}
+		if a > 1 {
+			a = 1
+		}
+		// This algorithm comes from the standard library's image/draw package.
+		dstA := uint32(dst[i]) * 0x101
+		maskA := uint32(almost65536 * a)
+		outA := dstA*(0xffff-maskA)/0xffff + maskA
+		dst[i] = uint8(outA >> 8)
+	}
+}
diff --git a/vector/vector.go b/vector/vector.go
index 218e764..2a47905 100644
--- a/vector/vector.go
+++ b/vector/vector.go
@@ -207,8 +207,12 @@ func (z *Rasterizer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp
 		switch dst := dst.(type) {
 		case *image.Alpha:
 			// Fast path for glyph rendering.
-			if srcA == 0xffff && z.DrawOp == draw.Src {
-				z.rasterizeDstAlphaSrcOpaqueOpSrc(dst, r)
+			if srcA == 0xffff {
+				if z.DrawOp == draw.Over {
+					z.rasterizeDstAlphaSrcOpaqueOpOver(dst, r)
+				} else {
+					z.rasterizeDstAlphaSrcOpaqueOpSrc(dst, r)
+				}
 				return
 			}
 		}
@@ -221,7 +225,18 @@ func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpSrc(dst *image.Alpha, r image.R
 	// TODO: add a fixed point math implementation.
 	// TODO: non-zero vs even-odd winding?
 	if r == dst.Bounds() && r == z.Bounds() {
-		floatingAccumulate(dst.Pix, z.area)
+		floatingAccumulateOpSrc(dst.Pix, z.area)
+		return
+	}
+	println("TODO: the general case")
+}
+
+func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpOver(dst *image.Alpha, r image.Rectangle) {
+	// TODO: add SIMD implementations.
+	// TODO: add a fixed point math implementation.
+	// TODO: non-zero vs even-odd winding?
+	if r == dst.Bounds() && r == z.Bounds() {
+		floatingAccumulateOpOver(dst.Pix, z.area)
 		return
 	}
 	println("TODO: the general case")
diff --git a/vector/vector_test.go b/vector/vector_test.go
index 3aa8c19..7facf40 100644
--- a/vector/vector_test.go
+++ b/vector/vector_test.go
@@ -31,7 +31,7 @@ func encodePNG(dstFilename string, src image.Image) error {
 }
 
 func TestBasicPath(t *testing.T) {
-	want := []byte{
+	mask := []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, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xaa, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -50,27 +50,46 @@ func TestBasicPath(t *testing.T) {
 		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.CubeTo(f32.Vec2{8, 2}, f32.Vec2{5, 20}, f32.Vec2{2, 8})
-	z.ClosePath()
+	for _, background := range []uint8{0x00, 0x80} {
+		for _, op := range []draw.Op{draw.Over, draw.Src} {
+			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.CubeTo(f32.Vec2{8, 2}, f32.Vec2{5, 20}, f32.Vec2{2, 8})
+			z.ClosePath()
 
-	dst := image.NewAlpha(z.Bounds())
-	z.DrawOp = draw.Src
-	z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
+			dst := image.NewAlpha(z.Bounds())
+			for i := range dst.Pix {
+				dst.Pix[i] = background
+			}
+			z.DrawOp = op
+			z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
+			got := dst.Pix
 
-	got := dst.Pix
-	if len(got) != len(want) {
-		t.Fatalf("len(got)=%d and len(want)=%d differ", len(got), len(want))
-	}
-	for i := range got {
-		delta := int(got[i]) - int(want[i])
-		// The +/- 2 allows different implementations to give different
-		// rounding errors.
-		if delta < -2 || +2 < delta {
-			t.Errorf("i=%d: got %#02x, want %#02x", i, got[i], want[i])
+			want := make([]byte, len(mask))
+			if op == draw.Over && background == 0x80 {
+				for i, ma := range mask {
+					want[i] = 0xff - (0xff-ma)/2
+				}
+			} else {
+				copy(want, mask)
+			}
+
+			if len(got) != len(want) {
+				t.Errorf("background=%#02x, op=%v: len(got)=%d and len(want)=%d differ",
+					background, op, len(got), len(want))
+				continue
+			}
+			for i := range got {
+				delta := int(got[i]) - int(want[i])
+				// The +/- 2 allows different implementations to give different
+				// rounding errors.
+				if delta < -2 || +2 < delta {
+					t.Errorf("background=%#02x, op=%v: i=%d: got %#02x, want %#02x",
+						background, op, i, got[i], want[i])
+				}
+			}
 		}
 	}
 }