2016-09-01 09:26:54 +02:00
|
|
|
// Copyright 2016 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package vector
|
|
|
|
|
|
|
|
// TODO: add tests for NaN and Inf coordinates.
|
|
|
|
|
|
|
|
import (
|
2016-09-24 09:05:28 +02:00
|
|
|
"fmt"
|
2016-09-01 09:26:54 +02:00
|
|
|
"image"
|
2016-09-23 03:03:00 +02:00
|
|
|
"image/color"
|
2016-09-01 09:26:54 +02:00
|
|
|
"image/draw"
|
2016-09-05 11:50:44 +02:00
|
|
|
"image/png"
|
2016-09-23 03:03:00 +02:00
|
|
|
"math"
|
2016-10-14 08:12:05 +02:00
|
|
|
"math/rand"
|
2016-09-05 11:50:44 +02:00
|
|
|
"os"
|
2016-09-24 11:07:04 +02:00
|
|
|
"path/filepath"
|
2016-09-01 09:26:54 +02:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"golang.org/x/image/math/f32"
|
|
|
|
)
|
|
|
|
|
2016-09-05 11:50:44 +02:00
|
|
|
// encodePNG is useful for manually debugging the tests.
|
|
|
|
func encodePNG(dstFilename string, src image.Image) error {
|
|
|
|
f, err := os.Create(dstFilename)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
encErr := png.Encode(f, src)
|
|
|
|
closeErr := f.Close()
|
|
|
|
if encErr != nil {
|
|
|
|
return encErr
|
|
|
|
}
|
|
|
|
return closeErr
|
|
|
|
}
|
|
|
|
|
2016-09-24 11:07:04 +02:00
|
|
|
func pointOnCircle(center, radius, index, number int) f32.Vec2 {
|
|
|
|
c := float64(center)
|
|
|
|
r := float64(radius)
|
|
|
|
i := float64(index)
|
|
|
|
n := float64(number)
|
|
|
|
return f32.Vec2{
|
|
|
|
float32(c + r*(math.Cos(2*math.Pi*i/n))),
|
|
|
|
float32(c + r*(math.Sin(2*math.Pi*i/n))),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRasterizeOutOfBounds(t *testing.T) {
|
|
|
|
// Set this to a non-empty string such as "/tmp" to manually inspect the
|
|
|
|
// rasterization.
|
|
|
|
//
|
|
|
|
// If empty, this test simply checks that calling LineTo with points out of
|
|
|
|
// the rasterizer's bounds doesn't panic.
|
|
|
|
const tmpDir = ""
|
|
|
|
|
|
|
|
const center, radius, n = 16, 20, 16
|
|
|
|
var z Rasterizer
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
for j := 1; j < n/2; j++ {
|
|
|
|
z.Reset(2*center, 2*center)
|
|
|
|
z.MoveTo(f32.Vec2{1 * center, 1 * center})
|
|
|
|
z.LineTo(pointOnCircle(center, radius, i+0, n))
|
|
|
|
z.LineTo(pointOnCircle(center, radius, i+j, n))
|
|
|
|
z.ClosePath()
|
|
|
|
|
|
|
|
z.MoveTo(f32.Vec2{0 * center, 0 * center})
|
|
|
|
z.LineTo(f32.Vec2{0 * center, 2 * center})
|
|
|
|
z.LineTo(f32.Vec2{2 * center, 2 * center})
|
|
|
|
z.LineTo(f32.Vec2{2 * center, 0 * center})
|
|
|
|
z.ClosePath()
|
|
|
|
|
|
|
|
dst := image.NewAlpha(z.Bounds())
|
|
|
|
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
|
|
|
|
|
|
|
if tmpDir == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
filename := filepath.Join(tmpDir, fmt.Sprintf("out-%02d-%02d.png", i, j))
|
|
|
|
if err := encodePNG(filename, dst); err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
t.Logf("wrote %s", filename)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRasterizePolygon(t *testing.T) {
|
|
|
|
var z Rasterizer
|
|
|
|
for radius := 4; radius <= 1024; radius *= 2 {
|
|
|
|
for n := 3; n <= 19; n += 4 {
|
|
|
|
z.Reset(2*radius, 2*radius)
|
|
|
|
z.MoveTo(f32.Vec2{
|
|
|
|
float32(2 * radius),
|
|
|
|
float32(1 * radius),
|
|
|
|
})
|
|
|
|
for i := 1; i < n; i++ {
|
|
|
|
z.LineTo(pointOnCircle(radius, radius, i, n))
|
|
|
|
}
|
|
|
|
z.ClosePath()
|
|
|
|
|
|
|
|
dst := image.NewAlpha(z.Bounds())
|
|
|
|
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
|
|
|
|
|
|
|
if err := checkCornersCenter(dst); err != nil {
|
|
|
|
t.Errorf("radius=%d, n=%d: %v", radius, n, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRasterizeAlmostAxisAligned(t *testing.T) {
|
|
|
|
z := NewRasterizer(8, 8)
|
|
|
|
z.MoveTo(f32.Vec2{2, 2})
|
|
|
|
z.LineTo(f32.Vec2{6, math.Nextafter32(2, 0)})
|
|
|
|
z.LineTo(f32.Vec2{6, 6})
|
|
|
|
z.LineTo(f32.Vec2{math.Nextafter32(2, 0), 6})
|
|
|
|
z.ClosePath()
|
|
|
|
|
|
|
|
dst := image.NewAlpha(z.Bounds())
|
|
|
|
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
|
|
|
|
|
|
|
if err := checkCornersCenter(dst); err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-13 09:47:49 +02:00
|
|
|
func TestRasterizeWideAlmostHorizontalLines(t *testing.T) {
|
|
|
|
var z Rasterizer
|
|
|
|
for i := uint(3); i < 16; i++ {
|
|
|
|
x := float32(int(1 << i))
|
|
|
|
|
|
|
|
z.Reset(8, 8)
|
|
|
|
z.MoveTo(f32.Vec2{-x, 3})
|
|
|
|
z.LineTo(f32.Vec2{+x, 4})
|
|
|
|
z.LineTo(f32.Vec2{+x, 6})
|
|
|
|
z.LineTo(f32.Vec2{-x, 6})
|
|
|
|
z.ClosePath()
|
|
|
|
|
|
|
|
dst := image.NewAlpha(z.Bounds())
|
|
|
|
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
|
|
|
|
|
|
|
if err := checkCornersCenter(dst); err != nil {
|
|
|
|
t.Errorf("i=%d: %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-14 08:12:05 +02:00
|
|
|
func TestRasterize30Degrees(t *testing.T) {
|
|
|
|
z := NewRasterizer(8, 8)
|
|
|
|
z.MoveTo(f32.Vec2{4, 4})
|
|
|
|
z.LineTo(f32.Vec2{8, 4})
|
|
|
|
z.LineTo(f32.Vec2{4, 6})
|
|
|
|
z.ClosePath()
|
|
|
|
|
|
|
|
dst := image.NewAlpha(z.Bounds())
|
|
|
|
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
|
|
|
|
|
|
|
if err := checkCornersCenter(dst); err != nil {
|
|
|
|
t.Error(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRasterizeRandomLineTos(t *testing.T) {
|
|
|
|
var z Rasterizer
|
|
|
|
for i := 5; i < 50; i++ {
|
|
|
|
n, rng := 0, rand.New(rand.NewSource(int64(i)))
|
|
|
|
|
|
|
|
z.Reset(i+2, i+2)
|
|
|
|
z.MoveTo(f32.Vec2{float32(i / 2), float32(i / 2)})
|
|
|
|
for ; rng.Intn(16) != 0; n++ {
|
|
|
|
x := 1 + rng.Intn(i)
|
|
|
|
y := 1 + rng.Intn(i)
|
|
|
|
z.LineTo(f32.Vec2{float32(x), float32(y)})
|
|
|
|
}
|
|
|
|
z.ClosePath()
|
|
|
|
|
|
|
|
dst := image.NewAlpha(z.Bounds())
|
|
|
|
z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
|
|
|
|
|
|
|
|
if err := checkCorners(dst); err != nil {
|
|
|
|
t.Errorf("i=%d (%d nodes): %v", i, n, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-24 11:07:04 +02:00
|
|
|
// checkCornersCenter checks that the corners of the image are all 0x00 and the
|
|
|
|
// center is 0xff.
|
|
|
|
func checkCornersCenter(m *image.Alpha) error {
|
2016-10-14 08:12:05 +02:00
|
|
|
if err := checkCorners(m); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
size := m.Bounds().Size()
|
|
|
|
center := m.Pix[(size.Y/2)*m.Stride+(size.X/2)]
|
|
|
|
if center != 0xff {
|
|
|
|
return fmt.Errorf("center: got %#02x, want 0xff", center)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkCorners checks that the corners of the image are all 0x00.
|
|
|
|
func checkCorners(m *image.Alpha) error {
|
2016-09-24 11:07:04 +02:00
|
|
|
size := m.Bounds().Size()
|
|
|
|
corners := [4]uint8{
|
|
|
|
m.Pix[(0*size.Y+0)*m.Stride+(0*size.X+0)],
|
|
|
|
m.Pix[(0*size.Y+0)*m.Stride+(1*size.X-1)],
|
|
|
|
m.Pix[(1*size.Y-1)*m.Stride+(0*size.X+0)],
|
|
|
|
m.Pix[(1*size.Y-1)*m.Stride+(1*size.X-1)],
|
|
|
|
}
|
|
|
|
if corners != [4]uint8{} {
|
|
|
|
return fmt.Errorf("corners were not all zero: %v", corners)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-23 10:00:01 +02:00
|
|
|
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,
|
|
|
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xaa, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x5f, 0x00, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x24, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x14, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x4a, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x66, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xe4, 0xff, 0xff, 0xff, 0xb6, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x0c, 0xf2, 0xff, 0xff, 0xfe, 0x9e, 0x15, 0x00, 0x15, 0x96, 0xff, 0xce, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x88, 0xfc, 0xe3, 0x43, 0x00, 0x00, 0x00, 0x00, 0x06, 0xcd, 0xdc, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xde, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 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,
|
|
|
|
}
|
|
|
|
|
2016-09-24 09:05:28 +02:00
|
|
|
func testBasicPath(t *testing.T, prefix string, dst draw.Image, src image.Image, op draw.Op, want []byte) {
|
2016-09-23 10:00:01 +02:00
|
|
|
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()
|
2016-09-24 09:05:28 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2016-09-23 10:00:01 +02:00
|
|
|
}
|
2016-09-01 09:26:54 +02:00
|
|
|
|
2016-09-23 10:00:01 +02:00
|
|
|
func TestBasicPathDstAlpha(t *testing.T) {
|
2016-09-21 14:15:02 +02:00
|
|
|
for _, background := range []uint8{0x00, 0x80} {
|
|
|
|
for _, op := range []draw.Op{draw.Over, draw.Src} {
|
2016-09-24 09:05:28 +02:00
|
|
|
for _, xPadding := range []int{0, 7} {
|
|
|
|
bounds := image.Rect(0, 0, 16+xPadding, 16)
|
|
|
|
dst := image.NewAlpha(bounds)
|
|
|
|
for i := range dst.Pix {
|
|
|
|
dst.Pix[i] = background
|
2016-09-21 14:15:02 +02:00
|
|
|
}
|
|
|
|
|
2016-09-24 09:05:28 +02:00
|
|
|
want := make([]byte, len(dst.Pix))
|
|
|
|
copy(want, dst.Pix)
|
|
|
|
|
|
|
|
if op == draw.Over && background == 0x80 {
|
|
|
|
for y := 0; y < 16; y++ {
|
|
|
|
for x := 0; x < 16; x++ {
|
|
|
|
ma := basicMask[16*y+x]
|
|
|
|
i := dst.PixOffset(x, y)
|
|
|
|
want[i] = 0xff - (0xff-ma)/2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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
|
|
|
|
}
|
|
|
|
}
|
2016-09-21 14:15:02 +02:00
|
|
|
}
|
2016-09-24 09:05:28 +02:00
|
|
|
|
|
|
|
prefix := fmt.Sprintf("background=%#02x, op=%v, xPadding=%d", background, op, xPadding)
|
|
|
|
testBasicPath(t, prefix, dst, image.Opaque, op, want)
|
2016-09-21 14:15:02 +02:00
|
|
|
}
|
2016-09-01 09:26:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-23 03:03:00 +02:00
|
|
|
|
2016-09-23 10:00:01 +02:00
|
|
|
func TestBasicPathDstRGBA(t *testing.T) {
|
2016-09-24 09:05:28 +02:00
|
|
|
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
|
2016-09-23 10:00:01 +02:00
|
|
|
|
|
|
|
for _, op := range []draw.Op{draw.Over, draw.Src} {
|
2016-09-24 09:05:28 +02:00
|
|
|
for _, xPadding := range []int{0, 7} {
|
|
|
|
bounds := image.Rect(0, 0, 16+xPadding, 16)
|
|
|
|
dst := image.NewRGBA(bounds)
|
|
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
|
|
dst.SetRGBA(x, y, color.RGBA{
|
|
|
|
R: uint8(y * 0x07),
|
|
|
|
G: uint8(x * 0x05),
|
|
|
|
B: 0x00,
|
|
|
|
A: 0x80,
|
|
|
|
})
|
|
|
|
}
|
2016-09-23 10:00:01 +02:00
|
|
|
}
|
|
|
|
|
2016-09-24 09:05:28 +02:00
|
|
|
want := make([]byte, len(dst.Pix))
|
|
|
|
copy(want, dst.Pix)
|
|
|
|
|
|
|
|
if op == draw.Over {
|
|
|
|
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] = 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
|
|
|
|
}
|
2016-09-23 10:00:01 +02:00
|
|
|
}
|
2016-09-24 09:05:28 +02:00
|
|
|
} 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
|
|
|
|
}
|
2016-09-23 10:00:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-24 09:05:28 +02:00
|
|
|
prefix := fmt.Sprintf("op=%v, xPadding=%d", op, xPadding)
|
|
|
|
testBasicPath(t, prefix, dst, blue, op, want)
|
2016-09-23 10:00:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-23 03:03:00 +02:00
|
|
|
const (
|
|
|
|
benchmarkGlyphWidth = 893
|
|
|
|
benchmarkGlyphHeight = 1122
|
|
|
|
)
|
|
|
|
|
2016-09-30 11:28:54 +02:00
|
|
|
type benchmarkGlyphDatum struct {
|
2016-09-23 03:03:00 +02:00
|
|
|
// n being 0, 1 or 2 means moveTo, lineTo or quadTo.
|
|
|
|
n uint32
|
|
|
|
p f32.Vec2
|
|
|
|
q f32.Vec2
|
2016-09-30 11:28:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// benchmarkGlyphData is the 'a' glyph from the Roboto Regular font, translated
|
|
|
|
// so that its top left corner is (0, 0).
|
|
|
|
var benchmarkGlyphData = []benchmarkGlyphDatum{
|
2016-09-23 03:03:00 +02:00
|
|
|
{0, f32.Vec2{699, 1102}, f32.Vec2{0, 0}},
|
|
|
|
{2, f32.Vec2{683, 1070}, f32.Vec2{673, 988}},
|
|
|
|
{2, f32.Vec2{544, 1122}, f32.Vec2{365, 1122}},
|
|
|
|
{2, f32.Vec2{205, 1122}, f32.Vec2{102.5, 1031.5}},
|
|
|
|
{2, f32.Vec2{0, 941}, f32.Vec2{0, 802}},
|
|
|
|
{2, f32.Vec2{0, 633}, f32.Vec2{128.5, 539.5}},
|
|
|
|
{2, f32.Vec2{257, 446}, f32.Vec2{490, 446}},
|
|
|
|
{1, f32.Vec2{670, 446}, f32.Vec2{0, 0}},
|
|
|
|
{1, f32.Vec2{670, 361}, f32.Vec2{0, 0}},
|
|
|
|
{2, f32.Vec2{670, 264}, f32.Vec2{612, 206.5}},
|
|
|
|
{2, f32.Vec2{554, 149}, f32.Vec2{441, 149}},
|
|
|
|
{2, f32.Vec2{342, 149}, f32.Vec2{275, 199}},
|
|
|
|
{2, f32.Vec2{208, 249}, f32.Vec2{208, 320}},
|
|
|
|
{1, f32.Vec2{22, 320}, f32.Vec2{0, 0}},
|
|
|
|
{2, f32.Vec2{22, 239}, f32.Vec2{79.5, 163.5}},
|
|
|
|
{2, f32.Vec2{137, 88}, f32.Vec2{235.5, 44}},
|
|
|
|
{2, f32.Vec2{334, 0}, f32.Vec2{452, 0}},
|
|
|
|
{2, f32.Vec2{639, 0}, f32.Vec2{745, 93.5}},
|
|
|
|
{2, f32.Vec2{851, 187}, f32.Vec2{855, 351}},
|
|
|
|
{1, f32.Vec2{855, 849}, f32.Vec2{0, 0}},
|
|
|
|
{2, f32.Vec2{855, 998}, f32.Vec2{893, 1086}},
|
|
|
|
{1, f32.Vec2{893, 1102}, f32.Vec2{0, 0}},
|
|
|
|
{1, f32.Vec2{699, 1102}, f32.Vec2{0, 0}},
|
|
|
|
{0, f32.Vec2{392, 961}, f32.Vec2{0, 0}},
|
|
|
|
{2, f32.Vec2{479, 961}, f32.Vec2{557, 916}},
|
|
|
|
{2, f32.Vec2{635, 871}, f32.Vec2{670, 799}},
|
|
|
|
{1, f32.Vec2{670, 577}, f32.Vec2{0, 0}},
|
|
|
|
{1, f32.Vec2{525, 577}, f32.Vec2{0, 0}},
|
|
|
|
{2, f32.Vec2{185, 577}, f32.Vec2{185, 776}},
|
|
|
|
{2, f32.Vec2{185, 863}, f32.Vec2{243, 912}},
|
|
|
|
{2, f32.Vec2{301, 961}, f32.Vec2{392, 961}},
|
|
|
|
}
|
|
|
|
|
2016-09-30 11:28:54 +02:00
|
|
|
func scaledBenchmarkGlyphData(height int) (width int, data []benchmarkGlyphDatum) {
|
2016-09-23 03:03:00 +02:00
|
|
|
scale := float32(height) / benchmarkGlyphHeight
|
|
|
|
|
|
|
|
// Clone the benchmarkGlyphData slice and scale its coordinates.
|
2016-09-30 11:28:54 +02:00
|
|
|
data = append(data, benchmarkGlyphData...)
|
2016-09-23 03:03:00 +02:00
|
|
|
for i := range data {
|
|
|
|
data[i].p[0] *= scale
|
|
|
|
data[i].p[1] *= scale
|
|
|
|
data[i].q[0] *= scale
|
|
|
|
data[i].q[1] *= scale
|
|
|
|
}
|
|
|
|
|
2016-09-30 11:28:54 +02:00
|
|
|
return int(math.Ceil(float64(benchmarkGlyphWidth * scale))), data
|
|
|
|
}
|
|
|
|
|
|
|
|
// benchGlyph benchmarks rasterizing a TrueType glyph.
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
// a glyph's vectors. A height of 64 corresponds to a ppem greater than 64.
|
|
|
|
func benchGlyph(b *testing.B, colorModel byte, loose bool, height int, op draw.Op) {
|
|
|
|
width, data := scaledBenchmarkGlyphData(height)
|
2016-09-23 03:03:00 +02:00
|
|
|
z := NewRasterizer(width, height)
|
|
|
|
|
2016-09-24 09:05:28 +02:00
|
|
|
bounds := z.Bounds()
|
|
|
|
if loose {
|
|
|
|
bounds.Max.X++
|
|
|
|
}
|
2016-09-23 03:03:00 +02:00
|
|
|
dst, src := draw.Image(nil), image.Image(nil)
|
2016-09-24 09:05:28 +02:00
|
|
|
switch colorModel {
|
|
|
|
case 'A':
|
|
|
|
dst = image.NewAlpha(bounds)
|
2016-09-23 03:03:00 +02:00
|
|
|
src = image.Opaque
|
2016-09-24 09:05:28 +02:00
|
|
|
case 'N':
|
|
|
|
dst = image.NewNRGBA(bounds)
|
2016-09-23 10:00:01 +02:00
|
|
|
src = image.NewUniform(color.NRGBA{0x40, 0x80, 0xc0, 0xff})
|
2016-09-24 09:05:28 +02:00
|
|
|
case 'R':
|
|
|
|
dst = image.NewRGBA(bounds)
|
2016-09-23 10:00:01 +02:00
|
|
|
src = image.NewUniform(color.RGBA{0x40, 0x80, 0xc0, 0xff})
|
2016-09-23 03:03:00 +02:00
|
|
|
default:
|
|
|
|
b.Fatal("unsupported color model")
|
|
|
|
}
|
2016-09-24 09:05:28 +02:00
|
|
|
bounds = z.Bounds()
|
2016-09-23 03:03:00 +02:00
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
z.Reset(width, height)
|
|
|
|
z.DrawOp = op
|
|
|
|
for _, d := range data {
|
|
|
|
switch d.n {
|
|
|
|
case 0:
|
|
|
|
z.MoveTo(d.p)
|
|
|
|
case 1:
|
|
|
|
z.LineTo(d.p)
|
|
|
|
case 2:
|
|
|
|
z.QuadTo(d.p, d.q)
|
|
|
|
}
|
|
|
|
}
|
2016-09-24 09:05:28 +02:00
|
|
|
z.Draw(dst, bounds, src, image.Point{})
|
2016-09-23 03:03:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-24 09:05:28 +02:00
|
|
|
func BenchmarkGlyphAlpha16Over(b *testing.B) { benchGlyph(b, 'A', false, 16, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlpha16Src(b *testing.B) { benchGlyph(b, 'A', false, 16, draw.Src) }
|
|
|
|
func BenchmarkGlyphAlpha32Over(b *testing.B) { benchGlyph(b, 'A', false, 32, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlpha32Src(b *testing.B) { benchGlyph(b, 'A', false, 32, draw.Src) }
|
|
|
|
func BenchmarkGlyphAlpha64Over(b *testing.B) { benchGlyph(b, 'A', false, 64, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlpha64Src(b *testing.B) { benchGlyph(b, 'A', false, 64, draw.Src) }
|
|
|
|
func BenchmarkGlyphAlpha128Over(b *testing.B) { benchGlyph(b, 'A', false, 128, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlpha128Src(b *testing.B) { benchGlyph(b, 'A', false, 128, draw.Src) }
|
|
|
|
func BenchmarkGlyphAlpha256Over(b *testing.B) { benchGlyph(b, 'A', false, 256, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlpha256Src(b *testing.B) { benchGlyph(b, 'A', false, 256, draw.Src) }
|
|
|
|
|
|
|
|
func BenchmarkGlyphAlphaLoose16Over(b *testing.B) { benchGlyph(b, 'A', true, 16, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlphaLoose16Src(b *testing.B) { benchGlyph(b, 'A', true, 16, draw.Src) }
|
|
|
|
func BenchmarkGlyphAlphaLoose32Over(b *testing.B) { benchGlyph(b, 'A', true, 32, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlphaLoose32Src(b *testing.B) { benchGlyph(b, 'A', true, 32, draw.Src) }
|
|
|
|
func BenchmarkGlyphAlphaLoose64Over(b *testing.B) { benchGlyph(b, 'A', true, 64, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlphaLoose64Src(b *testing.B) { benchGlyph(b, 'A', true, 64, draw.Src) }
|
|
|
|
func BenchmarkGlyphAlphaLoose128Over(b *testing.B) { benchGlyph(b, 'A', true, 128, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlphaLoose128Src(b *testing.B) { benchGlyph(b, 'A', true, 128, draw.Src) }
|
|
|
|
func BenchmarkGlyphAlphaLoose256Over(b *testing.B) { benchGlyph(b, 'A', true, 256, draw.Over) }
|
|
|
|
func BenchmarkGlyphAlphaLoose256Src(b *testing.B) { benchGlyph(b, 'A', true, 256, draw.Src) }
|
2016-09-23 03:03:00 +02:00
|
|
|
|
2016-09-24 09:05:28 +02:00
|
|
|
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) }
|
2016-09-28 11:32:24 +02:00
|
|
|
|
|
|
|
func BenchmarkGlyphNRGBA16Over(b *testing.B) { benchGlyph(b, 'N', false, 16, draw.Over) }
|
|
|
|
func BenchmarkGlyphNRGBA16Src(b *testing.B) { benchGlyph(b, 'N', false, 16, draw.Src) }
|
|
|
|
func BenchmarkGlyphNRGBA32Over(b *testing.B) { benchGlyph(b, 'N', false, 32, draw.Over) }
|
|
|
|
func BenchmarkGlyphNRGBA32Src(b *testing.B) { benchGlyph(b, 'N', false, 32, draw.Src) }
|
|
|
|
func BenchmarkGlyphNRGBA64Over(b *testing.B) { benchGlyph(b, 'N', false, 64, draw.Over) }
|
|
|
|
func BenchmarkGlyphNRGBA64Src(b *testing.B) { benchGlyph(b, 'N', false, 64, draw.Src) }
|
|
|
|
func BenchmarkGlyphNRGBA128Over(b *testing.B) { benchGlyph(b, 'N', false, 128, draw.Over) }
|
|
|
|
func BenchmarkGlyphNRGBA128Src(b *testing.B) { benchGlyph(b, 'N', false, 128, draw.Src) }
|
|
|
|
func BenchmarkGlyphNRGBA256Over(b *testing.B) { benchGlyph(b, 'N', false, 256, draw.Over) }
|
|
|
|
func BenchmarkGlyphNRGBA256Src(b *testing.B) { benchGlyph(b, 'N', false, 256, draw.Src) }
|