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

commitb077ed42b318a9435f52cfb7c85abc4131d8d262
parent50ce1ebf58
authorNigel Tao <nigeltao@golang.org>
date2016-09-23 18:00
vector: implement arbitrary dst and src image types.

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

 vector/raster_floating.go |  17 +++++-
 vector/vector.go          |  98 ++++++++++++++++++++++++++----
 vector/vector_test.go     | 150 ++++++++++++++++++++++++++++++++++++----------
 3 files changed, 223 insertions(+), 42 deletions(-)

diff --git a/vector/raster_floating.go b/vector/raster_floating.go
index 8171edc..721cfcb 100644
--- a/vector/raster_floating.go
+++ b/vector/raster_floating.go
@@ -61,7 +61,7 @@ func (z *Rasterizer) floatingLineTo(b f32.Vec2) {
 			x = xNext
 			continue
 		}
-		buf := z.area[y*width:]
+		buf := z.bufF32[y*width:]
 		d := dy * dir
 		x0, x1 := x, xNext
 		if x > xNext {
@@ -178,3 +178,18 @@ func floatingAccumulateOpOver(dst []uint8, src []float32) {
 		dst[i] = uint8(outA >> 8)
 	}
 }
+
+func floatingAccumulateMask(dst []uint32, src []float32) {
+	acc := float32(0)
+	for i, v := range src {
+		acc += v
+		a := acc
+		if a < 0 {
+			a = -a
+		}
+		if a > 1 {
+			a = 1
+		}
+		dst[i] = uint32(almost65536 * a)
+	}
+}
diff --git a/vector/vector.go b/vector/vector.go
index 2a47905..a4504a0 100644
--- a/vector/vector.go
+++ b/vector/vector.go
@@ -18,6 +18,7 @@ package vector // import "golang.org/x/image/vector"
 
 import (
 	"image"
+	"image/color"
 	"image/draw"
 	"math"
 
@@ -52,14 +53,32 @@ func clamp(i, width int32) uint {
 // by the given width and height.
 func NewRasterizer(w, h int) *Rasterizer {
 	return &Rasterizer{
-		area: make([]float32, w*h),
-		size: image.Point{w, h},
+		bufF32: make([]float32, w*h),
+		size:   image.Point{w, h},
 	}
 }
 
 // Raster is a 2-D vector graphics rasterizer.
 type Rasterizer struct {
-	area  []float32
+	// bufXxx are buffers of float32 or uint32 values, holding either the
+	// individual or cumulative area values.
+	//
+	// We don't actually need both values at any given time, and to conserve
+	// memory, the integration of the individual to the cumulative could modify
+	// the buffer in place. In other words, we could use a single buffer, say
+	// of type []uint32, and add some math.Float32bits and math.Float32frombits
+	// calls to satisfy the compiler's type checking. As of Go 1.7, though,
+	// there is a performance penalty between:
+	//	bufF32[i] += x
+	// and
+	//	bufU32[i] = math.Float32bits(x + math.Float32frombits(bufU32[i]))
+	//
+	// See golang.org/issue/17220 for some discussion.
+	//
+	// TODO: use bufU32 in the fixed point math implementation.
+	bufF32 []float32
+	bufU32 []uint32
+
 	size  image.Point
 	first f32.Vec2
 	pen   f32.Vec2
@@ -77,12 +96,12 @@ type Rasterizer struct {
 //
 // This includes setting z.DrawOp to draw.Over.
 func (z *Rasterizer) Reset(w, h int) {
-	if n := w * h; n > cap(z.area) {
-		z.area = make([]float32, n)
+	if n := w * h; n > cap(z.bufF32) {
+		z.bufF32 = make([]float32, n)
 	} else {
-		z.area = z.area[:n]
-		for i := range z.area {
-			z.area[i] = 0
+		z.bufF32 = z.bufF32[:n]
+		for i := range z.bufF32 {
+			z.bufF32[i] = 0
 		}
 	}
 	z.size = image.Point{w, h}
@@ -202,6 +221,9 @@ func devSquared(a, b, c f32.Vec2) float32 {
 // The vector paths previously added via the XxxTo calls become the mask for
 // drawing src onto dst.
 func (z *Rasterizer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) {
+	// TODO: adjust r and sp (and mp?) if src.Bounds() doesn't contain
+	// r.Add(sp.Sub(r.Min)).
+
 	if src, ok := src.(*image.Uniform); ok {
 		_, _, _, srcA := src.RGBA()
 		switch dst := dst.(type) {
@@ -217,7 +239,19 @@ func (z *Rasterizer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp
 			}
 		}
 	}
-	println("TODO: the general case")
+
+	if n := z.size.X * z.size.Y; n > cap(z.bufU32) {
+		z.bufU32 = make([]uint32, n)
+	} else {
+		z.bufU32 = z.bufU32[:n]
+	}
+	floatingAccumulateMask(z.bufU32, z.bufF32)
+
+	if z.DrawOp == draw.Over {
+		z.rasterizeOpOver(dst, r, src, sp)
+	} else {
+		z.rasterizeOpSrc(dst, r, src, sp)
+	}
 }
 
 func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpSrc(dst *image.Alpha, r image.Rectangle) {
@@ -225,7 +259,7 @@ 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() {
-		floatingAccumulateOpSrc(dst.Pix, z.area)
+		floatingAccumulateOpSrc(dst.Pix, z.bufF32)
 		return
 	}
 	println("TODO: the general case")
@@ -236,8 +270,50 @@ func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpOver(dst *image.Alpha, r image.
 	// 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)
+		floatingAccumulateOpOver(dst.Pix, z.bufF32)
 		return
 	}
 	println("TODO: the general case")
 }
+
+func (z *Rasterizer) rasterizeOpOver(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) {
+	out := color.RGBA64{}
+	outc := color.Color(&out)
+	for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ {
+		for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ {
+			sr, sg, sb, sa := src.At(sp.X+x, sp.Y+y).RGBA()
+			ma := z.bufU32[y*z.size.X+x]
+
+			// This algorithm comes from the standard library's image/draw
+			// package.
+			dr, dg, db, da := dst.At(r.Min.X+x, r.Min.Y+y).RGBA()
+			a := 0xffff - (sa * ma / 0xffff)
+			out.R = uint16((dr*a + sr*ma) / 0xffff)
+			out.G = uint16((dg*a + sg*ma) / 0xffff)
+			out.B = uint16((db*a + sb*ma) / 0xffff)
+			out.A = uint16((da*a + sa*ma) / 0xffff)
+
+			dst.Set(r.Min.X+x, r.Min.Y+y, outc)
+		}
+	}
+}
+
+func (z *Rasterizer) rasterizeOpSrc(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) {
+	out := color.RGBA64{}
+	outc := color.Color(&out)
+	for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ {
+		for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ {
+			sr, sg, sb, sa := src.At(sp.X+x, sp.Y+y).RGBA()
+			ma := z.bufU32[y*z.size.X+x]
+
+			// This algorithm comes from the standard library's image/draw
+			// package.
+			out.R = uint16(sr * ma / 0xffff)
+			out.G = uint16(sg * ma / 0xffff)
+			out.B = uint16(sb * ma / 0xffff)
+			out.A = uint16(sa * ma / 0xffff)
+
+			dst.Set(r.Min.X+x, r.Min.Y+y, outc)
+		}
+	}
+}
diff --git a/vector/vector_test.go b/vector/vector_test.go
index 604b8c1..354de33 100644
--- a/vector/vector_test.go
+++ b/vector/vector_test.go
@@ -32,35 +32,39 @@ func encodePNG(dstFilename string, src image.Image) error {
 	return closeErr
 }
 
-func TestBasicPath(t *testing.T) {
-	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,
-		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,
-	}
+var basicMask = []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,
+	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,
+}
+
+func basicRasterizer() *Rasterizer {
+	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()
+	return z
+}
 
+func TestBasicPathDstAlpha(t *testing.T) {
 	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()
-
+			z := basicRasterizer()
 			dst := image.NewAlpha(z.Bounds())
 			for i := range dst.Pix {
 				dst.Pix[i] = background
@@ -69,13 +73,13 @@ func TestBasicPath(t *testing.T) {
 			z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
 			got := dst.Pix
 
-			want := make([]byte, len(mask))
+			want := make([]byte, len(basicMask))
 			if op == draw.Over && background == 0x80 {
-				for i, ma := range mask {
+				for i, ma := range basicMask {
 					want[i] = 0xff - (0xff-ma)/2
 				}
 			} else {
-				copy(want, mask)
+				copy(want, basicMask)
 			}
 
 			if len(got) != len(want) {
@@ -96,6 +100,66 @@ func TestBasicPath(t *testing.T) {
 	}
 }
 
+func TestBasicPathDstRGBA(t *testing.T) {
+	blue := color.RGBA{0x00, 0x00, 0xff, 0xff}
+
+	for _, op := range []draw.Op{draw.Over, draw.Src} {
+		z := basicRasterizer()
+		dst := image.NewRGBA(z.Bounds())
+		for y := 0; y < 16; y++ {
+			for x := 0; x < 16; x++ {
+				dst.SetRGBA(x, y, color.RGBA{
+					R: uint8(y*0x11) / 2,
+					G: uint8(x*0x11) / 2,
+					B: 0x00,
+					A: 0x80,
+				})
+			}
+		}
+		z.DrawOp = op
+		z.Draw(dst, dst.Bounds(), image.NewUniform(blue), image.Point{})
+		got := dst.Pix
+
+		want := make([]byte, len(basicMask)*4)
+		if op == draw.Over {
+			for y := 0; y < 16; y++ {
+				for x := 0; x < 16; x++ {
+					i := 16*y + x
+					ma := basicMask[i]
+					want[4*i+0] = uint8((uint32(0xff-ma) * uint32(y*0x11/2)) / 0xff)
+					want[4*i+1] = uint8((uint32(0xff-ma) * uint32(x*0x11/2)) / 0xff)
+					want[4*i+2] = ma
+					want[4*i+3] = ma/2 + 0x80
+				}
+			}
+		} else {
+			for y := 0; y < 16; y++ {
+				for x := 0; x < 16; x++ {
+					i := 16*y + x
+					ma := basicMask[i]
+					want[4*i+0] = 0x00
+					want[4*i+1] = 0x00
+					want[4*i+2] = ma
+					want[4*i+3] = ma
+				}
+			}
+		}
+
+		if len(got) != len(want) {
+			t.Errorf("op=%v: len(got)=%d and len(want)=%d differ", 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("op=%v: i=%d: got %#02x, want %#02x", op, i, got[i], want[i])
+			}
+		}
+	}
+}
+
 const (
 	benchmarkGlyphWidth  = 893
 	benchmarkGlyphHeight = 1122
@@ -167,6 +231,12 @@ func benchGlyph(b *testing.B, cm color.Model, height int, op draw.Op) {
 	case color.AlphaModel:
 		dst = image.NewAlpha(z.Bounds())
 		src = image.Opaque
+	case color.NRGBAModel:
+		dst = image.NewNRGBA(z.Bounds())
+		src = image.NewUniform(color.NRGBA{0x40, 0x80, 0xc0, 0xff})
+	case color.RGBAModel:
+		dst = image.NewRGBA(z.Bounds())
+		src = image.NewUniform(color.RGBA{0x40, 0x80, 0xc0, 0xff})
 	default:
 		b.Fatal("unsupported color model")
 	}
@@ -200,4 +270,24 @@ func BenchmarkGlyphAlpha128Src(b *testing.B)  { benchGlyph(b, color.AlphaModel,
 func BenchmarkGlyphAlpha256Over(b *testing.B) { benchGlyph(b, color.AlphaModel, 256, draw.Over) }
 func BenchmarkGlyphAlpha256Src(b *testing.B)  { benchGlyph(b, color.AlphaModel, 256, draw.Src) }
 
-// TODO: color.RGBAModel benchmarks.
+func BenchmarkGlyphNRGBA16Over(b *testing.B)  { benchGlyph(b, color.NRGBAModel, 16, draw.Over) }
+func BenchmarkGlyphNRGBA16Src(b *testing.B)   { benchGlyph(b, color.NRGBAModel, 16, draw.Src) }
+func BenchmarkGlyphNRGBA32Over(b *testing.B)  { benchGlyph(b, color.NRGBAModel, 32, draw.Over) }
+func BenchmarkGlyphNRGBA32Src(b *testing.B)   { benchGlyph(b, color.NRGBAModel, 32, draw.Src) }
+func BenchmarkGlyphNRGBA64Over(b *testing.B)  { benchGlyph(b, color.NRGBAModel, 64, draw.Over) }
+func BenchmarkGlyphNRGBA64Src(b *testing.B)   { benchGlyph(b, color.NRGBAModel, 64, draw.Src) }
+func BenchmarkGlyphNRGBA128Over(b *testing.B) { benchGlyph(b, color.NRGBAModel, 128, draw.Over) }
+func BenchmarkGlyphNRGBA128Src(b *testing.B)  { benchGlyph(b, color.NRGBAModel, 128, draw.Src) }
+func BenchmarkGlyphNRGBA256Over(b *testing.B) { benchGlyph(b, color.NRGBAModel, 256, draw.Over) }
+func BenchmarkGlyphNRGBA256Src(b *testing.B)  { benchGlyph(b, color.NRGBAModel, 256, draw.Src) }
+
+func BenchmarkGlyphRGBA16Over(b *testing.B)  { benchGlyph(b, color.RGBAModel, 16, draw.Over) }
+func BenchmarkGlyphRGBA16Src(b *testing.B)   { benchGlyph(b, color.RGBAModel, 16, draw.Src) }
+func BenchmarkGlyphRGBA32Over(b *testing.B)  { benchGlyph(b, color.RGBAModel, 32, draw.Over) }
+func BenchmarkGlyphRGBA32Src(b *testing.B)   { benchGlyph(b, color.RGBAModel, 32, draw.Src) }
+func BenchmarkGlyphRGBA64Over(b *testing.B)  { benchGlyph(b, color.RGBAModel, 64, draw.Over) }
+func BenchmarkGlyphRGBA64Src(b *testing.B)   { benchGlyph(b, color.RGBAModel, 64, draw.Src) }
+func BenchmarkGlyphRGBA128Over(b *testing.B) { benchGlyph(b, color.RGBAModel, 128, draw.Over) }
+func BenchmarkGlyphRGBA128Src(b *testing.B)  { benchGlyph(b, color.RGBAModel, 128, draw.Src) }
+func BenchmarkGlyphRGBA256Over(b *testing.B) { benchGlyph(b, color.RGBAModel, 256, draw.Over) }
+func BenchmarkGlyphRGBA256Src(b *testing.B)  { benchGlyph(b, color.RGBAModel, 256, draw.Src) }