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]) + } + } } } }