golang-image/vector/vector_test.go

509 lines
16 KiB
Go
Raw Normal View History

// 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 (
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"math"
vector: fix overflow when rasterizing a 30 degree line. There are some obvious TODOs, but they will be follow-up commits. This is about correctness, not performance, but for the record: name old time/op new time/op delta GlyphAlpha16Over-8 3.16µs ± 0% 3.38µs ± 0% +6.96% (p=0.000 n=9+10) GlyphAlpha16Src-8 3.06µs ± 0% 3.28µs ± 0% +7.21% (p=0.000 n=10+10) GlyphAlpha32Over-8 4.92µs ± 1% 5.23µs ± 1% +6.24% (p=0.000 n=10+10) GlyphAlpha32Src-8 4.53µs ± 1% 4.83µs ± 0% +6.69% (p=0.000 n=10+10) GlyphAlpha64Over-8 9.60µs ± 1% 10.21µs ± 0% +6.36% (p=0.000 n=10+10) GlyphAlpha64Src-8 8.04µs ± 0% 8.68µs ± 1% +7.99% (p=0.000 n=9+10) GlyphAlpha128Over-8 23.1µs ± 0% 24.2µs ± 1% +5.08% (p=0.000 n=9+10) GlyphAlpha128Src-8 16.8µs ± 0% 18.0µs ± 1% +6.76% (p=0.000 n=10+10) GlyphAlpha256Over-8 68.6µs ± 1% 70.3µs ± 0% +2.50% (p=0.000 n=10+10) GlyphAlpha256Src-8 43.6µs ± 0% 45.4µs ± 0% +4.08% (p=0.000 n=10+8) GlyphRGBA16Over-8 4.92µs ± 0% 5.14µs ± 0% +4.48% (p=0.000 n=9+9) GlyphRGBA16Src-8 4.39µs ± 0% 4.59µs ± 0% +4.60% (p=0.000 n=8+9) GlyphRGBA32Over-8 11.8µs ± 0% 12.2µs ± 1% +2.89% (p=0.000 n=9+10) GlyphRGBA32Src-8 9.79µs ± 1% 10.03µs ± 0% +2.49% (p=0.000 n=10+7) GlyphRGBA64Over-8 36.7µs ± 1% 37.5µs ± 1% +2.23% (p=0.000 n=10+10) GlyphRGBA64Src-8 28.5µs ± 0% 29.1µs ± 0% +2.09% (p=0.000 n=10+10) GlyphRGBA128Over-8 133µs ± 0% 135µs ± 0% +1.51% (p=0.000 n=10+9) GlyphRGBA128Src-8 99.1µs ± 0% 100.5µs ± 1% +1.47% (p=0.000 n=9+10) GlyphRGBA256Over-8 505µs ± 0% 511µs ± 0% +1.18% (p=0.000 n=9+10) GlyphRGBA256Src-8 372µs ± 0% 374µs ± 0% +0.69% (p=0.000 n=9+10) Change-Id: Ice1d77de5bc2649f8cd88366bcae3c00e78d65c2 Reviewed-on: https://go-review.googlesource.com/31113 Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-14 08:12:05 +02:00
"math/rand"
"os"
"path/filepath"
"testing"
)
// 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
}
func pointOnCircle(center, radius, index, number int) (x, y float32) {
c := float64(center)
r := float64(radius)
i := float64(index)
n := float64(number)
return 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(1*center, 1*center)
z.LineTo(pointOnCircle(center, radius, i+0, n))
z.LineTo(pointOnCircle(center, radius, i+j, n))
z.ClosePath()
z.MoveTo(0*center, 0*center)
z.LineTo(0*center, 2*center)
z.LineTo(2*center, 2*center)
z.LineTo(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
vector: change ϕ from 10 to 9. This slight loss in quality allows us to use int32 math exclusively throughout raster_fixed.go, instead of occasionally dropping into int64 math. The change in ϕ doesn't affect the benchmarks noticably, but staying in int32 does. The net effect: name old time/op new time/op delta GlyphAlpha16Over-8 3.36µs ± 0% 2.99µs ± 0% -10.89% (p=0.000 n=10+9) GlyphAlpha16Src-8 3.26µs ± 0% 2.89µs ± 1% -11.34% (p=0.000 n=9+10) GlyphAlpha32Over-8 5.20µs ± 0% 4.53µs ± 0% -12.76% (p=0.000 n=8+10) GlyphAlpha32Src-8 4.81µs ± 1% 4.14µs ± 0% -13.91% (p=0.000 n=9+9) GlyphAlpha64Over-8 10.2µs ± 0% 9.0µs ± 1% -11.99% (p=0.000 n=9+10) GlyphAlpha64Src-8 8.62µs ± 0% 7.42µs ± 1% -13.89% (p=0.000 n=9+10) GlyphAlpha128Over-8 24.1µs ± 0% 21.8µs ± 0% -9.32% (p=0.000 n=9+9) GlyphAlpha128Src-8 17.9µs ± 0% 15.6µs ± 0% -12.68% (p=0.000 n=9+10) GlyphAlpha256Over-8 70.1µs ± 0% 66.3µs ± 1% -5.44% (p=0.000 n=10+10) GlyphAlpha256Src-8 45.2µs ± 1% 41.2µs ± 1% -8.92% (p=0.000 n=10+10) GlyphRGBA16Over-8 5.12µs ± 0% 4.75µs ± 0% -7.15% (p=0.000 n=10+9) GlyphRGBA16Src-8 4.57µs ± 1% 4.20µs ± 0% -8.18% (p=0.000 n=9+8) GlyphRGBA32Over-8 12.1µs ± 0% 11.4µs ± 0% -5.50% (p=0.000 n=10+9) GlyphRGBA32Src-8 10.0µs ± 0% 9.3µs ± 1% -6.80% (p=0.000 n=10+9) GlyphRGBA64Over-8 37.2µs ± 0% 36.0µs ± 0% -3.17% (p=0.000 n=9+8) GlyphRGBA64Src-8 29.0µs ± 1% 27.9µs ± 1% -4.05% (p=0.000 n=9+10) GlyphRGBA128Over-8 134µs ± 1% 131µs ± 0% -1.85% (p=0.000 n=9+9) GlyphRGBA128Src-8 100µs ± 1% 98µs ± 0% -2.27% (p=0.000 n=10+9) GlyphRGBA256Over-8 506µs ± 0% 503µs ± 0% -0.56% (p=0.000 n=10+8) GlyphRGBA256Src-8 373µs ± 0% 370µs ± 0% -1.01% (p=0.000 n=10+9) Change-Id: Ie02afac6fd6fa95f090bf3fe0a5c744799ea9dc5 Reviewed-on: https://go-review.googlesource.com/31532 Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-20 01:34:23 +02:00
for radius := 4; radius <= 256; radius *= 2 {
for n := 3; n <= 19; n += 4 {
z.Reset(2*radius, 2*radius)
z.MoveTo(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(2, 2)
z.LineTo(6, math.Nextafter32(2, 0))
z.LineTo(6, 6)
z.LineTo(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)
}
}
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(-x, 3)
z.LineTo(+x, 4)
z.LineTo(+x, 6)
z.LineTo(-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)
}
}
}
vector: fix overflow when rasterizing a 30 degree line. There are some obvious TODOs, but they will be follow-up commits. This is about correctness, not performance, but for the record: name old time/op new time/op delta GlyphAlpha16Over-8 3.16µs ± 0% 3.38µs ± 0% +6.96% (p=0.000 n=9+10) GlyphAlpha16Src-8 3.06µs ± 0% 3.28µs ± 0% +7.21% (p=0.000 n=10+10) GlyphAlpha32Over-8 4.92µs ± 1% 5.23µs ± 1% +6.24% (p=0.000 n=10+10) GlyphAlpha32Src-8 4.53µs ± 1% 4.83µs ± 0% +6.69% (p=0.000 n=10+10) GlyphAlpha64Over-8 9.60µs ± 1% 10.21µs ± 0% +6.36% (p=0.000 n=10+10) GlyphAlpha64Src-8 8.04µs ± 0% 8.68µs ± 1% +7.99% (p=0.000 n=9+10) GlyphAlpha128Over-8 23.1µs ± 0% 24.2µs ± 1% +5.08% (p=0.000 n=9+10) GlyphAlpha128Src-8 16.8µs ± 0% 18.0µs ± 1% +6.76% (p=0.000 n=10+10) GlyphAlpha256Over-8 68.6µs ± 1% 70.3µs ± 0% +2.50% (p=0.000 n=10+10) GlyphAlpha256Src-8 43.6µs ± 0% 45.4µs ± 0% +4.08% (p=0.000 n=10+8) GlyphRGBA16Over-8 4.92µs ± 0% 5.14µs ± 0% +4.48% (p=0.000 n=9+9) GlyphRGBA16Src-8 4.39µs ± 0% 4.59µs ± 0% +4.60% (p=0.000 n=8+9) GlyphRGBA32Over-8 11.8µs ± 0% 12.2µs ± 1% +2.89% (p=0.000 n=9+10) GlyphRGBA32Src-8 9.79µs ± 1% 10.03µs ± 0% +2.49% (p=0.000 n=10+7) GlyphRGBA64Over-8 36.7µs ± 1% 37.5µs ± 1% +2.23% (p=0.000 n=10+10) GlyphRGBA64Src-8 28.5µs ± 0% 29.1µs ± 0% +2.09% (p=0.000 n=10+10) GlyphRGBA128Over-8 133µs ± 0% 135µs ± 0% +1.51% (p=0.000 n=10+9) GlyphRGBA128Src-8 99.1µs ± 0% 100.5µs ± 1% +1.47% (p=0.000 n=9+10) GlyphRGBA256Over-8 505µs ± 0% 511µs ± 0% +1.18% (p=0.000 n=9+10) GlyphRGBA256Src-8 372µs ± 0% 374µs ± 0% +0.69% (p=0.000 n=9+10) Change-Id: Ice1d77de5bc2649f8cd88366bcae3c00e78d65c2 Reviewed-on: https://go-review.googlesource.com/31113 Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-14 08:12:05 +02:00
func TestRasterize30Degrees(t *testing.T) {
z := NewRasterizer(8, 8)
z.MoveTo(4, 4)
z.LineTo(8, 4)
z.LineTo(4, 6)
vector: fix overflow when rasterizing a 30 degree line. There are some obvious TODOs, but they will be follow-up commits. This is about correctness, not performance, but for the record: name old time/op new time/op delta GlyphAlpha16Over-8 3.16µs ± 0% 3.38µs ± 0% +6.96% (p=0.000 n=9+10) GlyphAlpha16Src-8 3.06µs ± 0% 3.28µs ± 0% +7.21% (p=0.000 n=10+10) GlyphAlpha32Over-8 4.92µs ± 1% 5.23µs ± 1% +6.24% (p=0.000 n=10+10) GlyphAlpha32Src-8 4.53µs ± 1% 4.83µs ± 0% +6.69% (p=0.000 n=10+10) GlyphAlpha64Over-8 9.60µs ± 1% 10.21µs ± 0% +6.36% (p=0.000 n=10+10) GlyphAlpha64Src-8 8.04µs ± 0% 8.68µs ± 1% +7.99% (p=0.000 n=9+10) GlyphAlpha128Over-8 23.1µs ± 0% 24.2µs ± 1% +5.08% (p=0.000 n=9+10) GlyphAlpha128Src-8 16.8µs ± 0% 18.0µs ± 1% +6.76% (p=0.000 n=10+10) GlyphAlpha256Over-8 68.6µs ± 1% 70.3µs ± 0% +2.50% (p=0.000 n=10+10) GlyphAlpha256Src-8 43.6µs ± 0% 45.4µs ± 0% +4.08% (p=0.000 n=10+8) GlyphRGBA16Over-8 4.92µs ± 0% 5.14µs ± 0% +4.48% (p=0.000 n=9+9) GlyphRGBA16Src-8 4.39µs ± 0% 4.59µs ± 0% +4.60% (p=0.000 n=8+9) GlyphRGBA32Over-8 11.8µs ± 0% 12.2µs ± 1% +2.89% (p=0.000 n=9+10) GlyphRGBA32Src-8 9.79µs ± 1% 10.03µs ± 0% +2.49% (p=0.000 n=10+7) GlyphRGBA64Over-8 36.7µs ± 1% 37.5µs ± 1% +2.23% (p=0.000 n=10+10) GlyphRGBA64Src-8 28.5µs ± 0% 29.1µs ± 0% +2.09% (p=0.000 n=10+10) GlyphRGBA128Over-8 133µs ± 0% 135µs ± 0% +1.51% (p=0.000 n=10+9) GlyphRGBA128Src-8 99.1µs ± 0% 100.5µs ± 1% +1.47% (p=0.000 n=9+10) GlyphRGBA256Over-8 505µs ± 0% 511µs ± 0% +1.18% (p=0.000 n=9+10) GlyphRGBA256Src-8 372µs ± 0% 374µs ± 0% +0.69% (p=0.000 n=9+10) Change-Id: Ice1d77de5bc2649f8cd88366bcae3c00e78d65c2 Reviewed-on: https://go-review.googlesource.com/31113 Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-14 08:12:05 +02:00
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(float32(i/2), float32(i/2))
vector: fix overflow when rasterizing a 30 degree line. There are some obvious TODOs, but they will be follow-up commits. This is about correctness, not performance, but for the record: name old time/op new time/op delta GlyphAlpha16Over-8 3.16µs ± 0% 3.38µs ± 0% +6.96% (p=0.000 n=9+10) GlyphAlpha16Src-8 3.06µs ± 0% 3.28µs ± 0% +7.21% (p=0.000 n=10+10) GlyphAlpha32Over-8 4.92µs ± 1% 5.23µs ± 1% +6.24% (p=0.000 n=10+10) GlyphAlpha32Src-8 4.53µs ± 1% 4.83µs ± 0% +6.69% (p=0.000 n=10+10) GlyphAlpha64Over-8 9.60µs ± 1% 10.21µs ± 0% +6.36% (p=0.000 n=10+10) GlyphAlpha64Src-8 8.04µs ± 0% 8.68µs ± 1% +7.99% (p=0.000 n=9+10) GlyphAlpha128Over-8 23.1µs ± 0% 24.2µs ± 1% +5.08% (p=0.000 n=9+10) GlyphAlpha128Src-8 16.8µs ± 0% 18.0µs ± 1% +6.76% (p=0.000 n=10+10) GlyphAlpha256Over-8 68.6µs ± 1% 70.3µs ± 0% +2.50% (p=0.000 n=10+10) GlyphAlpha256Src-8 43.6µs ± 0% 45.4µs ± 0% +4.08% (p=0.000 n=10+8) GlyphRGBA16Over-8 4.92µs ± 0% 5.14µs ± 0% +4.48% (p=0.000 n=9+9) GlyphRGBA16Src-8 4.39µs ± 0% 4.59µs ± 0% +4.60% (p=0.000 n=8+9) GlyphRGBA32Over-8 11.8µs ± 0% 12.2µs ± 1% +2.89% (p=0.000 n=9+10) GlyphRGBA32Src-8 9.79µs ± 1% 10.03µs ± 0% +2.49% (p=0.000 n=10+7) GlyphRGBA64Over-8 36.7µs ± 1% 37.5µs ± 1% +2.23% (p=0.000 n=10+10) GlyphRGBA64Src-8 28.5µs ± 0% 29.1µs ± 0% +2.09% (p=0.000 n=10+10) GlyphRGBA128Over-8 133µs ± 0% 135µs ± 0% +1.51% (p=0.000 n=10+9) GlyphRGBA128Src-8 99.1µs ± 0% 100.5µs ± 1% +1.47% (p=0.000 n=9+10) GlyphRGBA256Over-8 505µs ± 0% 511µs ± 0% +1.18% (p=0.000 n=9+10) GlyphRGBA256Src-8 372µs ± 0% 374µs ± 0% +0.69% (p=0.000 n=9+10) Change-Id: Ice1d77de5bc2649f8cd88366bcae3c00e78d65c2 Reviewed-on: https://go-review.googlesource.com/31113 Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-14 08:12:05 +02:00
for ; rng.Intn(16) != 0; n++ {
x := 1 + rng.Intn(i)
y := 1 + rng.Intn(i)
z.LineTo(float32(x), float32(y))
vector: fix overflow when rasterizing a 30 degree line. There are some obvious TODOs, but they will be follow-up commits. This is about correctness, not performance, but for the record: name old time/op new time/op delta GlyphAlpha16Over-8 3.16µs ± 0% 3.38µs ± 0% +6.96% (p=0.000 n=9+10) GlyphAlpha16Src-8 3.06µs ± 0% 3.28µs ± 0% +7.21% (p=0.000 n=10+10) GlyphAlpha32Over-8 4.92µs ± 1% 5.23µs ± 1% +6.24% (p=0.000 n=10+10) GlyphAlpha32Src-8 4.53µs ± 1% 4.83µs ± 0% +6.69% (p=0.000 n=10+10) GlyphAlpha64Over-8 9.60µs ± 1% 10.21µs ± 0% +6.36% (p=0.000 n=10+10) GlyphAlpha64Src-8 8.04µs ± 0% 8.68µs ± 1% +7.99% (p=0.000 n=9+10) GlyphAlpha128Over-8 23.1µs ± 0% 24.2µs ± 1% +5.08% (p=0.000 n=9+10) GlyphAlpha128Src-8 16.8µs ± 0% 18.0µs ± 1% +6.76% (p=0.000 n=10+10) GlyphAlpha256Over-8 68.6µs ± 1% 70.3µs ± 0% +2.50% (p=0.000 n=10+10) GlyphAlpha256Src-8 43.6µs ± 0% 45.4µs ± 0% +4.08% (p=0.000 n=10+8) GlyphRGBA16Over-8 4.92µs ± 0% 5.14µs ± 0% +4.48% (p=0.000 n=9+9) GlyphRGBA16Src-8 4.39µs ± 0% 4.59µs ± 0% +4.60% (p=0.000 n=8+9) GlyphRGBA32Over-8 11.8µs ± 0% 12.2µs ± 1% +2.89% (p=0.000 n=9+10) GlyphRGBA32Src-8 9.79µs ± 1% 10.03µs ± 0% +2.49% (p=0.000 n=10+7) GlyphRGBA64Over-8 36.7µs ± 1% 37.5µs ± 1% +2.23% (p=0.000 n=10+10) GlyphRGBA64Src-8 28.5µs ± 0% 29.1µs ± 0% +2.09% (p=0.000 n=10+10) GlyphRGBA128Over-8 133µs ± 0% 135µs ± 0% +1.51% (p=0.000 n=10+9) GlyphRGBA128Src-8 99.1µs ± 0% 100.5µs ± 1% +1.47% (p=0.000 n=9+10) GlyphRGBA256Over-8 505µs ± 0% 511µs ± 0% +1.18% (p=0.000 n=9+10) GlyphRGBA256Src-8 372µs ± 0% 374µs ± 0% +0.69% (p=0.000 n=9+10) Change-Id: Ice1d77de5bc2649f8cd88366bcae3c00e78d65c2 Reviewed-on: https://go-review.googlesource.com/31113 Reviewed-by: David Crawshaw <crawshaw@golang.org>
2016-10-14 08:12:05 +02:00
}
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)
}
}
}
// checkCornersCenter checks that the corners of the image are all 0x00 and the
// center is 0xff.
func checkCornersCenter(m *image.Alpha) error {
vector: fix overflow when rasterizing a 30 degree line. There are some obvious TODOs, but they will be follow-up commits. This is about correctness, not performance, but for the record: name old time/op new time/op delta GlyphAlpha16Over-8 3.16µs ± 0% 3.38µs ± 0% +6.96% (p=0.000 n=9+10) GlyphAlpha16Src-8 3.06µs ± 0% 3.28µs ± 0% +7.21% (p=0.000 n=10+10) GlyphAlpha32Over-8 4.92µs ± 1% 5.23µs ± 1% +6.24% (p=0.000 n=10+10) GlyphAlpha32Src-8 4.53µs ± 1% 4.83µs ± 0% +6.69% (p=0.000 n=10+10) GlyphAlpha64Over-8 9.60µs ± 1% 10.21µs ± 0% +6.36% (p=0.000 n=10+10) GlyphAlpha64Src-8 8.04µs ± 0% 8.68µs ± 1% +7.99% (p=0.000 n=9+10) GlyphAlpha128Over-8 23.1µs ± 0% 24.2µs ± 1% +5.08% (p=0.000 n=9+10) GlyphAlpha128Src-8 16.8µs ± 0% 18.0µs ± 1% +6.76% (p=0.000 n=10+10) GlyphAlpha256Over-8 68.6µs ± 1% 70.3µs ± 0% +2.50% (p=0.000 n=10+10) GlyphAlpha256Src-8 43.6µs ± 0% 45.4µs ± 0% +4.08% (p=0.000 n=10+8) GlyphRGBA16Over-8 4.92µs ± 0% 5.14µs ± 0% +4.48% (p=0.000 n=9+9) GlyphRGBA16Src-8 4.39µs ± 0% 4.59µs ± 0% +4.60% (p=0.000 n=8+9) GlyphRGBA32Over-8 11.8µs ± 0% 12.2µs ± 1% +2.89% (p=0.000 n=9+10) GlyphRGBA32Src-8 9.79µs ± 1% 10.03µs ± 0% +2.49% (p=0.000 n=10+7) GlyphRGBA64Over-8 36.7µs ± 1% 37.5µs ± 1% +2.23% (p=0.000 n=10+10) GlyphRGBA64Src-8 28.5µs ± 0% 29.1µs ± 0% +2.09% (p=0.000 n=10+10) GlyphRGBA128Over-8 133µs ± 0% 135µs ± 0% +1.51% (p=0.000 n=10+9) GlyphRGBA128Src-8 99.1µs ± 0% 100.5µs ± 1% +1.47% (p=0.000 n=9+10) GlyphRGBA256Over-8 505µs ± 0% 511µs ± 0% +1.18% (p=0.000 n=9+10) GlyphRGBA256Src-8 372µs ± 0% 374µs ± 0% +0.69% (p=0.000 n=9+10) Change-Id: Ice1d77de5bc2649f8cd88366bcae3c00e78d65c2 Reviewed-on: https://go-review.googlesource.com/31113 Reviewed-by: David Crawshaw <crawshaw@golang.org>
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 {
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
}
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,
}
func testBasicPath(t *testing.T, prefix string, dst draw.Image, src image.Image, op draw.Op, want []byte) {
z := NewRasterizer(16, 16)
z.MoveTo(2, 2)
z.LineTo(8, 2)
z.QuadTo(14, 2, 14, 14)
z.CubeTo(8, 2, 5, 20, 2, 8)
z.ClosePath()
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) {
for _, background := range []uint8{0x00, 0x80} {
for _, op := range []draw.Op{draw.Over, draw.Src} {
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
}
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
}
}
}
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) {
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
for _, op := range []draw.Op{draw.Over, draw.Src} {
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,
})
}
}
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
}
}
} 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
}
}
}
prefix := fmt.Sprintf("op=%v, xPadding=%d", op, xPadding)
testBasicPath(t, prefix, dst, blue, op, want)
}
}
}
const (
benchmarkGlyphWidth = 893
benchmarkGlyphHeight = 1122
)
type benchmarkGlyphDatum struct {
// n being 0, 1 or 2 means moveTo, lineTo or quadTo.
n uint32
px float32
py float32
qx float32
qy float32
}
// benchmarkGlyphData is the 'a' glyph from the Roboto Regular font, translated
// so that its top left corner is (0, 0).
var benchmarkGlyphData = []benchmarkGlyphDatum{
{0, 699, 1102, 0, 0},
{2, 683, 1070, 673, 988},
{2, 544, 1122, 365, 1122},
{2, 205, 1122, 102.5, 1031.5},
{2, 0, 941, 0, 802},
{2, 0, 633, 128.5, 539.5},
{2, 257, 446, 490, 446},
{1, 670, 446, 0, 0},
{1, 670, 361, 0, 0},
{2, 670, 264, 612, 206.5},
{2, 554, 149, 441, 149},
{2, 342, 149, 275, 199},
{2, 208, 249, 208, 320},
{1, 22, 320, 0, 0},
{2, 22, 239, 79.5, 163.5},
{2, 137, 88, 235.5, 44},
{2, 334, 0, 452, 0},
{2, 639, 0, 745, 93.5},
{2, 851, 187, 855, 351},
{1, 855, 849, 0, 0},
{2, 855, 998, 893, 1086},
{1, 893, 1102, 0, 0},
{1, 699, 1102, 0, 0},
{0, 392, 961, 0, 0},
{2, 479, 961, 557, 916},
{2, 635, 871, 670, 799},
{1, 670, 577, 0, 0},
{1, 525, 577, 0, 0},
{2, 185, 577, 185, 776},
{2, 185, 863, 243, 912},
{2, 301, 961, 392, 961},
}
func scaledBenchmarkGlyphData(height int) (width int, data []benchmarkGlyphDatum) {
scale := float32(height) / benchmarkGlyphHeight
// Clone the benchmarkGlyphData slice and scale its coordinates.
data = append(data, benchmarkGlyphData...)
for i := range data {
data[i].px *= scale
data[i].py *= scale
data[i].qx *= scale
data[i].qy *= scale
}
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)
z := NewRasterizer(width, height)
bounds := z.Bounds()
if loose {
bounds.Max.X++
}
dst, src := draw.Image(nil), image.Image(nil)
switch colorModel {
case 'A':
dst = image.NewAlpha(bounds)
src = image.Opaque
case 'N':
dst = image.NewNRGBA(bounds)
src = image.NewUniform(color.NRGBA{0x40, 0x80, 0xc0, 0xff})
case 'R':
dst = image.NewRGBA(bounds)
src = image.NewUniform(color.RGBA{0x40, 0x80, 0xc0, 0xff})
default:
b.Fatal("unsupported color model")
}
bounds = z.Bounds()
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.px, d.py)
case 1:
z.LineTo(d.px, d.py)
case 2:
z.QuadTo(d.px, d.py, d.qx, d.qy)
}
}
z.Draw(dst, bounds, src, image.Point{})
}
}
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) }
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) }
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) }