git clone https://git.lucas.co/go_mono.git
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 <[email protected]>
vector/raster_floating.go | 31 +++++++++++++++++++++++--
vector/vector.go | 21 ++++++++++++++---
vector/vector_test.go | 59 +++++++++++++++++++++++++++++++----------------
3 files changed, 86 insertions(+), 25 deletions(-)
diff --git a/vector/raster_floating.go b/vector/raster_floating.go
index d03936a..8171edc 100644
--- a/vector/raster_floating.go
+++ b/vector/raster_floating.go
@@ -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)
+ }
+}
diff --git a/vector/vector.go b/vector/vector.go
index 218e764..2a47905 100644
--- a/vector/vector.go
+++ b/vector/vector.go
@@ -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")
diff --git a/vector/vector_test.go b/vector/vector_test.go
index 3aa8c19..7facf40 100644
--- a/vector/vector_test.go
+++ b/vector/vector_test.go
@@ -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])
+ }
+ }
}
}
}