vector: allow dst and mask to have different strides.

Change-Id: If244dba2c681b11122a7a26b352f9411135f123f
Reviewed-on: https://go-review.googlesource.com/29696
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2016-09-24 17:05:28 +10:00
parent b077ed42b3
commit 7ea36498ac
2 changed files with 196 additions and 131 deletions

View File

@ -240,17 +240,45 @@ func (z *Rasterizer) Draw(dst draw.Image, r image.Rectangle, src image.Image, sp
} }
} }
if z.DrawOp == draw.Over {
z.rasterizeOpOver(dst, r, src, sp)
} else {
z.rasterizeOpSrc(dst, r, src, sp)
}
}
func (z *Rasterizer) accumulateMask() {
if n := z.size.X * z.size.Y; n > cap(z.bufU32) { if n := z.size.X * z.size.Y; n > cap(z.bufU32) {
z.bufU32 = make([]uint32, n) z.bufU32 = make([]uint32, n)
} else { } else {
z.bufU32 = z.bufU32[:n] z.bufU32 = z.bufU32[:n]
} }
floatingAccumulateMask(z.bufU32, z.bufF32) floatingAccumulateMask(z.bufU32, z.bufF32)
}
if z.DrawOp == draw.Over { func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpOver(dst *image.Alpha, r image.Rectangle) {
z.rasterizeOpOver(dst, r, src, sp) // TODO: add SIMD implementations.
} else { // TODO: add a fixed point math implementation.
z.rasterizeOpSrc(dst, r, src, sp) // TODO: non-zero vs even-odd winding?
if r == dst.Bounds() && r == z.Bounds() {
// We bypass the z.accumulateMask step and convert straight from
// z.bufF32 to dst.Pix.
floatingAccumulateOpOver(dst.Pix, z.bufF32)
return
}
z.accumulateMask()
pix := dst.Pix[dst.PixOffset(r.Min.X, r.Min.Y):]
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++ {
ma := z.bufU32[y*z.size.X+x]
i := y*dst.Stride + x
// This formula is like rasterizeOpOver's, simplified for the
// concrete dst type and opaque src assumption.
a := 0xffff - ma
pix[i] = uint8((uint32(pix[i])*0x101*a/0xffff + ma) >> 8)
}
} }
} }
@ -259,24 +287,27 @@ 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() {
// We bypass the z.accumulateMask step and convert straight from
// z.bufF32 to dst.Pix.
floatingAccumulateOpSrc(dst.Pix, z.bufF32) floatingAccumulateOpSrc(dst.Pix, z.bufF32)
return return
} }
println("TODO: the general case")
}
func (z *Rasterizer) rasterizeDstAlphaSrcOpaqueOpOver(dst *image.Alpha, r image.Rectangle) { z.accumulateMask()
// TODO: add SIMD implementations. pix := dst.Pix[dst.PixOffset(r.Min.X, r.Min.Y):]
// TODO: add a fixed point math implementation. for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ {
// TODO: non-zero vs even-odd winding? for x, x1 := 0, r.Max.X-r.Min.X; x < x1; x++ {
if r == dst.Bounds() && r == z.Bounds() { ma := z.bufU32[y*z.size.X+x]
floatingAccumulateOpOver(dst.Pix, z.bufF32)
return // This formula is like rasterizeOpSrc's, simplified for the
// concrete dst type and opaque src assumption.
pix[y*dst.Stride+x] = uint8(ma >> 8)
}
} }
println("TODO: the general case")
} }
func (z *Rasterizer) rasterizeOpOver(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { func (z *Rasterizer) rasterizeOpOver(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) {
z.accumulateMask()
out := color.RGBA64{} out := color.RGBA64{}
outc := color.Color(&out) outc := color.Color(&out)
for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ {
@ -299,6 +330,7 @@ func (z *Rasterizer) rasterizeOpOver(dst draw.Image, r image.Rectangle, src imag
} }
func (z *Rasterizer) rasterizeOpSrc(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { func (z *Rasterizer) rasterizeOpSrc(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) {
z.accumulateMask()
out := color.RGBA64{} out := color.RGBA64{}
outc := color.Color(&out) outc := color.Color(&out)
for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ { for y, y1 := 0, r.Max.Y-r.Min.Y; y < y1; y++ {

View File

@ -7,6 +7,7 @@ package vector
// TODO: add tests for NaN and Inf coordinates. // TODO: add tests for NaN and Inf coordinates.
import ( import (
"fmt"
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
@ -51,111 +52,127 @@ 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,
} }
func basicRasterizer() *Rasterizer { func testBasicPath(t *testing.T, prefix string, dst draw.Image, src image.Image, op draw.Op, want []byte) {
z := NewRasterizer(16, 16) z := NewRasterizer(16, 16)
z.MoveTo(f32.Vec2{2, 2}) z.MoveTo(f32.Vec2{2, 2})
z.LineTo(f32.Vec2{8, 2}) z.LineTo(f32.Vec2{8, 2})
z.QuadTo(f32.Vec2{14, 2}, f32.Vec2{14, 14}) 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.CubeTo(f32.Vec2{8, 2}, f32.Vec2{5, 20}, f32.Vec2{2, 8})
z.ClosePath() z.ClosePath()
return z
z.DrawOp = op
z.Draw(dst, z.Bounds(), src, image.Point{})
var got []byte
switch dst := dst.(type) {
case *image.Alpha:
got = dst.Pix
case *image.RGBA:
got = dst.Pix
default:
t.Errorf("%s: unrecognized dst image type %T", prefix, dst)
}
if len(got) != len(want) {
t.Errorf("%s: len(got)=%d and len(want)=%d differ", prefix, len(got), len(want))
return
}
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("%s: i=%d: got %#02x, want %#02x", prefix, i, got[i], want[i])
return
}
}
} }
func TestBasicPathDstAlpha(t *testing.T) { func TestBasicPathDstAlpha(t *testing.T) {
for _, background := range []uint8{0x00, 0x80} { for _, background := range []uint8{0x00, 0x80} {
for _, op := range []draw.Op{draw.Over, draw.Src} { for _, op := range []draw.Op{draw.Over, draw.Src} {
z := basicRasterizer() for _, xPadding := range []int{0, 7} {
dst := image.NewAlpha(z.Bounds()) bounds := image.Rect(0, 0, 16+xPadding, 16)
for i := range dst.Pix { dst := image.NewAlpha(bounds)
dst.Pix[i] = background for i := range dst.Pix {
} dst.Pix[i] = background
z.DrawOp = op
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
got := dst.Pix
want := make([]byte, len(basicMask))
if op == draw.Over && background == 0x80 {
for i, ma := range basicMask {
want[i] = 0xff - (0xff-ma)/2
} }
} else {
copy(want, basicMask)
}
if len(got) != len(want) { want := make([]byte, len(dst.Pix))
t.Errorf("background=%#02x, op=%v: len(got)=%d and len(want)=%d differ", copy(want, dst.Pix)
background, op, len(got), len(want))
continue if op == draw.Over && background == 0x80 {
} for y := 0; y < 16; y++ {
for i := range got { for x := 0; x < 16; x++ {
delta := int(got[i]) - int(want[i]) ma := basicMask[16*y+x]
// The +/- 2 allows different implementations to give different i := dst.PixOffset(x, y)
// rounding errors. want[i] = 0xff - (0xff-ma)/2
if delta < -2 || +2 < delta { }
t.Errorf("background=%#02x, op=%v: i=%d: got %#02x, want %#02x", }
background, op, i, got[i], want[i]) } else {
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
ma := basicMask[16*y+x]
i := dst.PixOffset(x, y)
want[i] = ma
}
}
} }
prefix := fmt.Sprintf("background=%#02x, op=%v, xPadding=%d", background, op, xPadding)
testBasicPath(t, prefix, dst, image.Opaque, op, want)
} }
} }
} }
} }
func TestBasicPathDstRGBA(t *testing.T) { func TestBasicPathDstRGBA(t *testing.T) {
blue := color.RGBA{0x00, 0x00, 0xff, 0xff} blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
for _, op := range []draw.Op{draw.Over, draw.Src} { for _, op := range []draw.Op{draw.Over, draw.Src} {
z := basicRasterizer() for _, xPadding := range []int{0, 7} {
dst := image.NewRGBA(z.Bounds()) bounds := image.Rect(0, 0, 16+xPadding, 16)
for y := 0; y < 16; y++ { dst := image.NewRGBA(bounds)
for x := 0; x < 16; x++ { for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
dst.SetRGBA(x, y, color.RGBA{ for x := bounds.Min.X; x < bounds.Max.X; x++ {
R: uint8(y*0x11) / 2, dst.SetRGBA(x, y, color.RGBA{
G: uint8(x*0x11) / 2, R: uint8(y * 0x07),
B: 0x00, G: uint8(x * 0x05),
A: 0x80, 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++ { want := make([]byte, len(dst.Pix))
for x := 0; x < 16; x++ { copy(want, dst.Pix)
i := 16*y + x
ma := basicMask[i] if op == draw.Over {
want[4*i+0] = 0x00 for y := 0; y < 16; y++ {
want[4*i+1] = 0x00 for x := 0; x < 16; x++ {
want[4*i+2] = ma ma := basicMask[16*y+x]
want[4*i+3] = ma i := dst.PixOffset(x, y)
want[i+0] = uint8((uint32(0xff-ma) * uint32(y*0x07)) / 0xff)
want[i+1] = uint8((uint32(0xff-ma) * uint32(x*0x05)) / 0xff)
want[i+2] = ma
want[i+3] = ma/2 + 0x80
}
}
} else {
for y := 0; y < 16; y++ {
for x := 0; x < 16; x++ {
ma := basicMask[16*y+x]
i := dst.PixOffset(x, y)
want[i+0] = 0x00
want[i+1] = 0x00
want[i+2] = ma
want[i+3] = ma
}
} }
} }
}
if len(got) != len(want) { prefix := fmt.Sprintf("op=%v, xPadding=%d", op, xPadding)
t.Errorf("op=%v: len(got)=%d and len(want)=%d differ", op, len(got), len(want)) testBasicPath(t, prefix, dst, blue, op, 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])
}
} }
} }
} }
@ -211,7 +228,7 @@ var benchmarkGlyphData = []struct {
// Note that, compared to the github.com/google/font-go prototype, the height // Note that, compared to the github.com/google/font-go prototype, the height
// here is the height of the bounding box, not the pixels per em used to scale // here is the height of the bounding box, not the pixels per em used to scale
// a glyph's vectors. A height of 64 corresponds to a ppem greater than 64. // a glyph's vectors. A height of 64 corresponds to a ppem greater than 64.
func benchGlyph(b *testing.B, cm color.Model, height int, op draw.Op) { func benchGlyph(b *testing.B, colorModel byte, loose bool, height int, op draw.Op) {
scale := float32(height) / benchmarkGlyphHeight scale := float32(height) / benchmarkGlyphHeight
// Clone the benchmarkGlyphData slice and scale its coordinates. // Clone the benchmarkGlyphData slice and scale its coordinates.
@ -226,20 +243,25 @@ func benchGlyph(b *testing.B, cm color.Model, height int, op draw.Op) {
width := int(math.Ceil(float64(benchmarkGlyphWidth * scale))) width := int(math.Ceil(float64(benchmarkGlyphWidth * scale)))
z := NewRasterizer(width, height) z := NewRasterizer(width, height)
bounds := z.Bounds()
if loose {
bounds.Max.X++
}
dst, src := draw.Image(nil), image.Image(nil) dst, src := draw.Image(nil), image.Image(nil)
switch cm { switch colorModel {
case color.AlphaModel: case 'A':
dst = image.NewAlpha(z.Bounds()) dst = image.NewAlpha(bounds)
src = image.Opaque src = image.Opaque
case color.NRGBAModel: case 'N':
dst = image.NewNRGBA(z.Bounds()) dst = image.NewNRGBA(bounds)
src = image.NewUniform(color.NRGBA{0x40, 0x80, 0xc0, 0xff}) src = image.NewUniform(color.NRGBA{0x40, 0x80, 0xc0, 0xff})
case color.RGBAModel: case 'R':
dst = image.NewRGBA(z.Bounds()) dst = image.NewRGBA(bounds)
src = image.NewUniform(color.RGBA{0x40, 0x80, 0xc0, 0xff}) src = image.NewUniform(color.RGBA{0x40, 0x80, 0xc0, 0xff})
default: default:
b.Fatal("unsupported color model") b.Fatal("unsupported color model")
} }
bounds = z.Bounds()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
@ -255,39 +277,50 @@ func benchGlyph(b *testing.B, cm color.Model, height int, op draw.Op) {
z.QuadTo(d.p, d.q) z.QuadTo(d.p, d.q)
} }
} }
z.Draw(dst, dst.Bounds(), src, image.Point{}) z.Draw(dst, bounds, src, image.Point{})
} }
} }
func BenchmarkGlyphAlpha16Over(b *testing.B) { benchGlyph(b, color.AlphaModel, 16, draw.Over) } func BenchmarkGlyphAlpha16Over(b *testing.B) { benchGlyph(b, 'A', false, 16, draw.Over) }
func BenchmarkGlyphAlpha16Src(b *testing.B) { benchGlyph(b, color.AlphaModel, 16, draw.Src) } func BenchmarkGlyphAlpha16Src(b *testing.B) { benchGlyph(b, 'A', false, 16, draw.Src) }
func BenchmarkGlyphAlpha32Over(b *testing.B) { benchGlyph(b, color.AlphaModel, 32, draw.Over) } func BenchmarkGlyphAlpha32Over(b *testing.B) { benchGlyph(b, 'A', false, 32, draw.Over) }
func BenchmarkGlyphAlpha32Src(b *testing.B) { benchGlyph(b, color.AlphaModel, 32, draw.Src) } func BenchmarkGlyphAlpha32Src(b *testing.B) { benchGlyph(b, 'A', false, 32, draw.Src) }
func BenchmarkGlyphAlpha64Over(b *testing.B) { benchGlyph(b, color.AlphaModel, 64, draw.Over) } func BenchmarkGlyphAlpha64Over(b *testing.B) { benchGlyph(b, 'A', false, 64, draw.Over) }
func BenchmarkGlyphAlpha64Src(b *testing.B) { benchGlyph(b, color.AlphaModel, 64, draw.Src) } func BenchmarkGlyphAlpha64Src(b *testing.B) { benchGlyph(b, 'A', false, 64, draw.Src) }
func BenchmarkGlyphAlpha128Over(b *testing.B) { benchGlyph(b, color.AlphaModel, 128, draw.Over) } func BenchmarkGlyphAlpha128Over(b *testing.B) { benchGlyph(b, 'A', false, 128, draw.Over) }
func BenchmarkGlyphAlpha128Src(b *testing.B) { benchGlyph(b, color.AlphaModel, 128, draw.Src) } func BenchmarkGlyphAlpha128Src(b *testing.B) { benchGlyph(b, 'A', false, 128, draw.Src) }
func BenchmarkGlyphAlpha256Over(b *testing.B) { benchGlyph(b, color.AlphaModel, 256, draw.Over) } func BenchmarkGlyphAlpha256Over(b *testing.B) { benchGlyph(b, 'A', false, 256, draw.Over) }
func BenchmarkGlyphAlpha256Src(b *testing.B) { benchGlyph(b, color.AlphaModel, 256, draw.Src) } func BenchmarkGlyphAlpha256Src(b *testing.B) { benchGlyph(b, 'A', false, 256, draw.Src) }
func BenchmarkGlyphNRGBA16Over(b *testing.B) { benchGlyph(b, color.NRGBAModel, 16, draw.Over) } func BenchmarkGlyphAlphaLoose16Over(b *testing.B) { benchGlyph(b, 'A', true, 16, draw.Over) }
func BenchmarkGlyphNRGBA16Src(b *testing.B) { benchGlyph(b, color.NRGBAModel, 16, draw.Src) } func BenchmarkGlyphAlphaLoose16Src(b *testing.B) { benchGlyph(b, 'A', true, 16, draw.Src) }
func BenchmarkGlyphNRGBA32Over(b *testing.B) { benchGlyph(b, color.NRGBAModel, 32, draw.Over) } func BenchmarkGlyphAlphaLoose32Over(b *testing.B) { benchGlyph(b, 'A', true, 32, draw.Over) }
func BenchmarkGlyphNRGBA32Src(b *testing.B) { benchGlyph(b, color.NRGBAModel, 32, draw.Src) } func BenchmarkGlyphAlphaLoose32Src(b *testing.B) { benchGlyph(b, 'A', true, 32, draw.Src) }
func BenchmarkGlyphNRGBA64Over(b *testing.B) { benchGlyph(b, color.NRGBAModel, 64, draw.Over) } func BenchmarkGlyphAlphaLoose64Over(b *testing.B) { benchGlyph(b, 'A', true, 64, draw.Over) }
func BenchmarkGlyphNRGBA64Src(b *testing.B) { benchGlyph(b, color.NRGBAModel, 64, draw.Src) } func BenchmarkGlyphAlphaLoose64Src(b *testing.B) { benchGlyph(b, 'A', true, 64, draw.Src) }
func BenchmarkGlyphNRGBA128Over(b *testing.B) { benchGlyph(b, color.NRGBAModel, 128, draw.Over) } func BenchmarkGlyphAlphaLoose128Over(b *testing.B) { benchGlyph(b, 'A', true, 128, draw.Over) }
func BenchmarkGlyphNRGBA128Src(b *testing.B) { benchGlyph(b, color.NRGBAModel, 128, draw.Src) } func BenchmarkGlyphAlphaLoose128Src(b *testing.B) { benchGlyph(b, 'A', true, 128, draw.Src) }
func BenchmarkGlyphNRGBA256Over(b *testing.B) { benchGlyph(b, color.NRGBAModel, 256, draw.Over) } func BenchmarkGlyphAlphaLoose256Over(b *testing.B) { benchGlyph(b, 'A', true, 256, draw.Over) }
func BenchmarkGlyphNRGBA256Src(b *testing.B) { benchGlyph(b, color.NRGBAModel, 256, draw.Src) } func BenchmarkGlyphAlphaLoose256Src(b *testing.B) { benchGlyph(b, 'A', true, 256, draw.Src) }
func BenchmarkGlyphRGBA16Over(b *testing.B) { benchGlyph(b, color.RGBAModel, 16, draw.Over) } func BenchmarkGlyphNRGBA16Over(b *testing.B) { benchGlyph(b, 'N', false, 16, draw.Over) }
func BenchmarkGlyphRGBA16Src(b *testing.B) { benchGlyph(b, color.RGBAModel, 16, draw.Src) } func BenchmarkGlyphNRGBA16Src(b *testing.B) { benchGlyph(b, 'N', false, 16, draw.Src) }
func BenchmarkGlyphRGBA32Over(b *testing.B) { benchGlyph(b, color.RGBAModel, 32, draw.Over) } func BenchmarkGlyphNRGBA32Over(b *testing.B) { benchGlyph(b, 'N', false, 32, draw.Over) }
func BenchmarkGlyphRGBA32Src(b *testing.B) { benchGlyph(b, color.RGBAModel, 32, draw.Src) } func BenchmarkGlyphNRGBA32Src(b *testing.B) { benchGlyph(b, 'N', false, 32, draw.Src) }
func BenchmarkGlyphRGBA64Over(b *testing.B) { benchGlyph(b, color.RGBAModel, 64, draw.Over) } func BenchmarkGlyphNRGBA64Over(b *testing.B) { benchGlyph(b, 'N', false, 64, draw.Over) }
func BenchmarkGlyphRGBA64Src(b *testing.B) { benchGlyph(b, color.RGBAModel, 64, draw.Src) } func BenchmarkGlyphNRGBA64Src(b *testing.B) { benchGlyph(b, 'N', false, 64, draw.Src) }
func BenchmarkGlyphRGBA128Over(b *testing.B) { benchGlyph(b, color.RGBAModel, 128, draw.Over) } func BenchmarkGlyphNRGBA128Over(b *testing.B) { benchGlyph(b, 'N', false, 128, draw.Over) }
func BenchmarkGlyphRGBA128Src(b *testing.B) { benchGlyph(b, color.RGBAModel, 128, draw.Src) } func BenchmarkGlyphNRGBA128Src(b *testing.B) { benchGlyph(b, 'N', false, 128, draw.Src) }
func BenchmarkGlyphRGBA256Over(b *testing.B) { benchGlyph(b, color.RGBAModel, 256, draw.Over) } func BenchmarkGlyphNRGBA256Over(b *testing.B) { benchGlyph(b, 'N', false, 256, draw.Over) }
func BenchmarkGlyphRGBA256Src(b *testing.B) { benchGlyph(b, color.RGBAModel, 256, draw.Src) } func BenchmarkGlyphNRGBA256Src(b *testing.B) { benchGlyph(b, 'N', false, 256, draw.Src) }
func BenchmarkGlyphRGBA16Over(b *testing.B) { benchGlyph(b, 'R', false, 16, draw.Over) }
func BenchmarkGlyphRGBA16Src(b *testing.B) { benchGlyph(b, 'R', false, 16, draw.Src) }
func BenchmarkGlyphRGBA32Over(b *testing.B) { benchGlyph(b, 'R', false, 32, draw.Over) }
func BenchmarkGlyphRGBA32Src(b *testing.B) { benchGlyph(b, 'R', false, 32, draw.Src) }
func BenchmarkGlyphRGBA64Over(b *testing.B) { benchGlyph(b, 'R', false, 64, draw.Over) }
func BenchmarkGlyphRGBA64Src(b *testing.B) { benchGlyph(b, 'R', false, 64, draw.Src) }
func BenchmarkGlyphRGBA128Over(b *testing.B) { benchGlyph(b, 'R', false, 128, draw.Over) }
func BenchmarkGlyphRGBA128Src(b *testing.B) { benchGlyph(b, 'R', false, 128, draw.Src) }
func BenchmarkGlyphRGBA256Over(b *testing.B) { benchGlyph(b, 'R', false, 256, draw.Over) }
func BenchmarkGlyphRGBA256Src(b *testing.B) { benchGlyph(b, 'R', false, 256, draw.Src) }