git clone https://git.lucas.co/go_mono.git
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) }