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
// value in the range [0x00, 0xff].
//
@ -136,8 +136,16 @@ func floatingAccumulate(dst []uint8, src []float32) {
// instead of the maximal value 0xff.
//
// 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)
for i, v := range src {
acc += v
@ -151,3 +159,22 @@ func floatingAccumulate(dst []uint8, src []float32) {
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) {
case *image.Alpha:
// Fast path for glyph rendering.
if srcA == 0xffff && z.DrawOp == draw.Src {
z.rasterizeDstAlphaSrcOpaqueOpSrc(dst, r)
if srcA == 0xffff {
if z.DrawOp == draw.Over {
z.rasterizeDstAlphaSrcOpaqueOpOver(dst, r)
} else {
z.rasterizeDstAlphaSrcOpaqueOpSrc(dst, r)
}
return
}
}
@ -221,7 +225,18 @@ 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() {
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
}
println("TODO: the general case")

View File

@ -31,7 +31,7 @@ func encodePNG(dstFilename string, src image.Image) error {
}
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, 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,
}
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()
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()
dst := image.NewAlpha(z.Bounds())
z.DrawOp = draw.Src
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
dst := image.NewAlpha(z.Bounds())
for i := range dst.Pix {
dst.Pix[i] = background
}
z.DrawOp = op
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
got := dst.Pix
got := dst.Pix
if len(got) != len(want) {
t.Fatalf("len(got)=%d and len(want)=%d differ", len(got), len(want))
}
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("i=%d: got %#02x, want %#02x", i, got[i], want[i])
want := make([]byte, len(mask))
if op == draw.Over && background == 0x80 {
for i, ma := range mask {
want[i] = 0xff - (0xff-ma)/2
}
} else {
copy(want, mask)
}
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])
}
}
}
}
}