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>
This commit is contained in:
Nigel Tao 2016-09-21 22:15:02 +10:00
parent bb355ba442
commit cd8486aac9
3 changed files with 86 additions and 25 deletions

View File

@ -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 // almost256 scales a floating point value in the range [0, 1] to a uint8
// value in the range [0x00, 0xff]. // value in the range [0x00, 0xff].
// //
@ -136,8 +136,16 @@ func floatingAccumulate(dst []uint8, src []float32) {
// instead of the maximal value 0xff. // instead of the maximal value 0xff.
// //
// math.Float32bits(almost256) is 0x437fffff. // 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) acc := float32(0)
for i, v := range src { for i, v := range src {
acc += v acc += v
@ -151,3 +159,22 @@ func floatingAccumulate(dst []uint8, src []float32) {
dst[i] = uint8(almost256 * a) 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)
}
}

View File

@ -207,8 +207,12 @@ func (z *Rasterizer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp
switch dst := dst.(type) { switch dst := dst.(type) {
case *image.Alpha: case *image.Alpha:
// Fast path for glyph rendering. // Fast path for glyph rendering.
if srcA == 0xffff && z.DrawOp == draw.Src { if srcA == 0xffff {
z.rasterizeDstAlphaSrcOpaqueOpSrc(dst, r) if z.DrawOp == draw.Over {
z.rasterizeDstAlphaSrcOpaqueOpOver(dst, r)
} else {
z.rasterizeDstAlphaSrcOpaqueOpSrc(dst, r)
}
return return
} }
} }
@ -221,7 +225,18 @@ func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpSrc(dst *image.Alpha, r image.R
// TODO: add a fixed point math implementation. // TODO: add a fixed point math implementation.
// TODO: non-zero vs even-odd winding? // TODO: non-zero vs even-odd winding?
if r == dst.Bounds() && r == z.Bounds() { 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 return
} }
println("TODO: the general case") println("TODO: the general case")

View File

@ -31,7 +31,7 @@ func encodePNG(dstFilename string, src image.Image) error {
} }
func TestBasicPath(t *testing.T) { 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, 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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
} }
z := NewRasterizer(16, 16) for _, background := range []uint8{0x00, 0x80} {
z.MoveTo(f32.Vec2{2, 2}) for _, op := range []draw.Op{draw.Over, draw.Src} {
z.LineTo(f32.Vec2{8, 2}) z := NewRasterizer(16, 16)
z.QuadTo(f32.Vec2{14, 2}, f32.Vec2{14, 14}) z.MoveTo(f32.Vec2{2, 2})
z.CubeTo(f32.Vec2{8, 2}, f32.Vec2{5, 20}, f32.Vec2{2, 8}) z.LineTo(f32.Vec2{8, 2})
z.ClosePath() 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()) dst := image.NewAlpha(z.Bounds())
z.DrawOp = draw.Src for i := range dst.Pix {
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{}) dst.Pix[i] = background
}
z.DrawOp = op
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
got := dst.Pix
got := dst.Pix want := make([]byte, len(mask))
if len(got) != len(want) { if op == draw.Over && background == 0x80 {
t.Fatalf("len(got)=%d and len(want)=%d differ", len(got), len(want)) for i, ma := range mask {
} want[i] = 0xff - (0xff-ma)/2
for i := range got { }
delta := int(got[i]) - int(want[i]) } else {
// The +/- 2 allows different implementations to give different copy(want, mask)
// rounding errors. }
if delta < -2 || +2 < delta {
t.Errorf("i=%d: got %#02x, want %#02x", i, got[i], want[i]) 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])
}
}
} }
} }
} }