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>
This commit is contained in:
Nigel Tao 2016-09-23 18:00:01 +10:00
parent 50ce1ebf58
commit b077ed42b3
3 changed files with 223 additions and 42 deletions

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View File

@ -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) }