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:
parent
50ce1ebf58
commit
b077ed42b3
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,7 @@ func encodePNG(dstFilename string, src image.Image) error {
|
|||
return closeErr
|
||||
}
|
||||
|
||||
func TestBasicPath(t *testing.T) {
|
||||
mask := []byte{
|
||||
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,
|
||||
|
@ -50,17 +49,22 @@ func TestBasicPath(t *testing.T) {
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
||||
for _, background := range []uint8{0x00, 0x80} {
|
||||
for _, op := range []draw.Op{draw.Over, draw.Src} {
|
||||
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 := 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) }
|
||||
|
|
Loading…
Reference in New Issue
Block a user