vector: make args float32 pairs instead of f32.Vec2.

The f32.Vec2 type doesn't seem worth it.

Change-Id: I021c7e13d7e2dd261334f4aa7e867df4fd8f1c3e
Reviewed-on: https://go-review.googlesource.com/32772
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Nigel Tao 2016-11-06 16:38:22 +11:00
parent c78039e8ce
commit 98f3e4e74d
5 changed files with 153 additions and 168 deletions

View File

@ -481,11 +481,11 @@ func makeInXxx(height int, useFloatingPointMath bool) *Rasterizer {
for _, d := range data { for _, d := range data {
switch d.n { switch d.n {
case 0: case 0:
z.MoveTo(d.p) z.MoveTo(d.px, d.py)
case 1: case 1:
z.LineTo(d.p) z.LineTo(d.px, d.py)
case 2: case 2:
z.QuadTo(d.p, d.q) z.QuadTo(d.px, d.py, d.qx, d.qy)
} }
} }
return z return z

View File

@ -7,10 +7,6 @@ package vector
// This file contains a fixed point math implementation of the vector // This file contains a fixed point math implementation of the vector
// graphics rasterizer. // graphics rasterizer.
import (
"golang.org/x/image/math/f32"
)
const ( const (
// ϕ is the number of binary digits after the fixed point. // ϕ is the number of binary digits after the fixed point.
// //
@ -58,35 +54,35 @@ func fixedMin(x, y int1ϕ) int1ϕ {
func fixedFloor(x int1ϕ) int32 { return int32(x >> ϕ) } func fixedFloor(x int1ϕ) int32 { return int32(x >> ϕ) }
func fixedCeil(x int1ϕ) int32 { return int32((x + fxOneMinusIota) >> ϕ) } func fixedCeil(x int1ϕ) int32 { return int32((x + fxOneMinusIota) >> ϕ) }
func (z *Rasterizer) fixedLineTo(b f32.Vec2) { func (z *Rasterizer) fixedLineTo(bx, by float32) {
a := z.pen ax, ay := z.penX, z.penY
z.pen = b z.penX, z.penY = bx, by
dir := int1ϕ(1) dir := int1ϕ(1)
if a[1] > b[1] { if ay > by {
dir, a, b = -1, b, a dir, ax, ay, bx, by = -1, bx, by, ax, ay
} }
// Horizontal line segments yield no change in coverage. Almost horizontal // Horizontal line segments yield no change in coverage. Almost horizontal
// segments would yield some change, in ideal math, but the computation // segments would yield some change, in ideal math, but the computation
// further below, involving 1 / (b[1] - a[1]), is unstable in fixed point // further below, involving 1 / (by - ay), is unstable in fixed point math,
// math, so we treat the segment as if it was perfectly horizontal. // so we treat the segment as if it was perfectly horizontal.
if b[1]-a[1] <= 0.000001 { if by-ay <= 0.000001 {
return return
} }
dxdy := (b[0] - a[0]) / (b[1] - a[1]) dxdy := (bx - ax) / (by - ay)
ay := int1ϕ(a[1] * float32(fxOne)) ayϕ := int1ϕ(ay * float32(fxOne))
by := int1ϕ(b[1] * float32(fxOne)) byϕ := int1ϕ(by * float32(fxOne))
x := int1ϕ(a[0] * float32(fxOne)) x := int1ϕ(ax * float32(fxOne))
y := fixedFloor(ay) y := fixedFloor(ayϕ)
yMax := fixedCeil(by) yMax := fixedCeil(byϕ)
if yMax > int32(z.size.Y) { if yMax > int32(z.size.Y) {
yMax = int32(z.size.Y) yMax = int32(z.size.Y)
} }
width := int32(z.size.X) width := int32(z.size.X)
for ; y < yMax; y++ { for ; y < yMax; y++ {
dy := fixedMin(int1ϕ(y+1)<<ϕ, by) - fixedMax(int1ϕ(y)<<ϕ, ay) dy := fixedMin(int1ϕ(y+1)<<ϕ, byϕ) - fixedMax(int1ϕ(y)<<ϕ, ayϕ)
xNext := x + int1ϕ(float32(dy)*dxdy) xNext := x + int1ϕ(float32(dy)*dxdy)
if y < 0 { if y < 0 {
x = xNext x = xNext

View File

@ -9,8 +9,6 @@ package vector
import ( import (
"math" "math"
"golang.org/x/image/math/f32"
) )
func floatingMax(x, y float32) float32 { func floatingMax(x, y float32) float32 {
@ -30,32 +28,32 @@ func floatingMin(x, y float32) float32 {
func floatingFloor(x float32) int32 { return int32(math.Floor(float64(x))) } func floatingFloor(x float32) int32 { return int32(math.Floor(float64(x))) }
func floatingCeil(x float32) int32 { return int32(math.Ceil(float64(x))) } func floatingCeil(x float32) int32 { return int32(math.Ceil(float64(x))) }
func (z *Rasterizer) floatingLineTo(b f32.Vec2) { func (z *Rasterizer) floatingLineTo(bx, by float32) {
a := z.pen ax, ay := z.penX, z.penY
z.pen = b z.penX, z.penY = bx, by
dir := float32(1) dir := float32(1)
if a[1] > b[1] { if ay > by {
dir, a, b = -1, b, a dir, ax, ay, bx, by = -1, bx, by, ax, ay
} }
// Horizontal line segments yield no change in coverage. Almost horizontal // Horizontal line segments yield no change in coverage. Almost horizontal
// segments would yield some change, in ideal math, but the computation // segments would yield some change, in ideal math, but the computation
// further below, involving 1 / (b[1] - a[1]), is unstable in floating // further below, involving 1 / (by - ay), is unstable in floating point
// point math, so we treat the segment as if it was perfectly horizontal. // math, so we treat the segment as if it was perfectly horizontal.
if b[1]-a[1] <= 0.000001 { if by-ay <= 0.000001 {
return return
} }
dxdy := (b[0] - a[0]) / (b[1] - a[1]) dxdy := (bx - ax) / (by - ay)
x := a[0] x := ax
y := floatingFloor(a[1]) y := floatingFloor(ay)
yMax := floatingCeil(b[1]) yMax := floatingCeil(by)
if yMax > int32(z.size.Y) { if yMax > int32(z.size.Y) {
yMax = int32(z.size.Y) yMax = int32(z.size.Y)
} }
width := int32(z.size.X) width := int32(z.size.X)
for ; y < yMax; y++ { for ; y < yMax; y++ {
dy := floatingMin(float32(y+1), b[1]) - floatingMax(float32(y), a[1]) dy := floatingMin(float32(y+1), by) - floatingMax(float32(y), ay)
xNext := x + dy*dxdy xNext := x + dy*dxdy
if y < 0 { if y < 0 {
x = xNext x = xNext

View File

@ -26,8 +26,6 @@ import (
"image/color" "image/color"
"image/draw" "image/draw"
"math" "math"
"golang.org/x/image/math/f32"
) )
// floatingPointMathThreshold is the width or height above which the rasterizer // floatingPointMathThreshold is the width or height above which the rasterizer
@ -50,18 +48,8 @@ import (
// would still produce acceptable quality, but 512 seems to work. // would still produce acceptable quality, but 512 seems to work.
const floatingPointMathThreshold = 512 const floatingPointMathThreshold = 512
func midPoint(p, q f32.Vec2) f32.Vec2 { func lerp(t, px, py, qx, qy float32) (x, y float32) {
return f32.Vec2{ return px + t*(qx-px), py + t*(qy-py)
(p[0] + q[0]) * 0.5,
(p[1] + q[1]) * 0.5,
}
}
func lerp(t float32, p, q f32.Vec2) f32.Vec2 {
return f32.Vec2{
p[0] + t*(q[0]-p[0]),
p[1] + t*(q[1]-p[1]),
}
} }
func clamp(i, width int32) uint { func clamp(i, width int32) uint {
@ -106,9 +94,11 @@ type Rasterizer struct {
useFloatingPointMath bool useFloatingPointMath bool
size image.Point size image.Point
first f32.Vec2 firstX float32
pen f32.Vec2 firstY float32
penX float32
penY float32
// DrawOp is the operator used for the Draw method. // DrawOp is the operator used for the Draw method.
// //
@ -124,8 +114,10 @@ type Rasterizer struct {
// This includes setting z.DrawOp to draw.Over. // This includes setting z.DrawOp to draw.Over.
func (z *Rasterizer) Reset(w, h int) { func (z *Rasterizer) Reset(w, h int) {
z.size = image.Point{w, h} z.size = image.Point{w, h}
z.first = f32.Vec2{} z.firstX = 0
z.pen = f32.Vec2{} z.firstY = 0
z.penX = 0
z.penY = 0
z.DrawOp = draw.Over z.DrawOp = draw.Over
z.setUseFloatingPointMath(w > floatingPointMathThreshold || h > floatingPointMathThreshold) z.setUseFloatingPointMath(w > floatingPointMathThreshold || h > floatingPointMathThreshold)
@ -169,63 +161,66 @@ func (z *Rasterizer) Bounds() image.Rectangle {
// Pen returns the location of the path-drawing pen: the last argument to the // Pen returns the location of the path-drawing pen: the last argument to the
// most recent XxxTo call. // most recent XxxTo call.
func (z *Rasterizer) Pen() f32.Vec2 { func (z *Rasterizer) Pen() (x, y float32) {
return z.pen return z.penX, z.penY
} }
// ClosePath closes the current path. // ClosePath closes the current path.
func (z *Rasterizer) ClosePath() { func (z *Rasterizer) ClosePath() {
z.LineTo(z.first) z.LineTo(z.firstX, z.firstY)
} }
// MoveTo starts a new path and moves the pen to a. // MoveTo starts a new path and moves the pen to (ax, ay).
// //
// The coordinates are allowed to be out of the Rasterizer's bounds. // The coordinates are allowed to be out of the Rasterizer's bounds.
func (z *Rasterizer) MoveTo(a f32.Vec2) { func (z *Rasterizer) MoveTo(ax, ay float32) {
z.first = a z.firstX = ax
z.pen = a z.firstY = ay
z.penX = ax
z.penY = ay
} }
// LineTo adds a line segment, from the pen to b, and moves the pen to b. // LineTo adds a line segment, from the pen to (bx, by), and moves the pen to
// (bx, by).
// //
// The coordinates are allowed to be out of the Rasterizer's bounds. // The coordinates are allowed to be out of the Rasterizer's bounds.
func (z *Rasterizer) LineTo(b f32.Vec2) { func (z *Rasterizer) LineTo(bx, by float32) {
if z.useFloatingPointMath { if z.useFloatingPointMath {
z.floatingLineTo(b) z.floatingLineTo(bx, by)
} else { } else {
z.fixedLineTo(b) z.fixedLineTo(bx, by)
} }
} }
// QuadTo adds a quadratic Bézier segment, from the pen via b to c, and moves // QuadTo adds a quadratic Bézier segment, from the pen via (bx, by) to (cx,
// the pen to c. // cy), and moves the pen to (cx, cy).
// //
// The coordinates are allowed to be out of the Rasterizer's bounds. // The coordinates are allowed to be out of the Rasterizer's bounds.
func (z *Rasterizer) QuadTo(b, c f32.Vec2) { func (z *Rasterizer) QuadTo(bx, by, cx, cy float32) {
a := z.pen ax, ay := z.penX, z.penY
devsq := devSquared(a, b, c) devsq := devSquared(ax, ay, bx, by, cx, cy)
if devsq >= 0.333 { if devsq >= 0.333 {
const tol = 3 const tol = 3
n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq)))) n := 1 + int(math.Sqrt(math.Sqrt(tol*float64(devsq))))
t, nInv := float32(0), 1/float32(n) t, nInv := float32(0), 1/float32(n)
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
t += nInv t += nInv
ab := lerp(t, a, b) abx, aby := lerp(t, ax, ay, bx, by)
bc := lerp(t, b, c) bcx, bcy := lerp(t, bx, by, cx, cy)
z.LineTo(lerp(t, ab, bc)) z.LineTo(lerp(t, abx, aby, bcx, bcy))
} }
} }
z.LineTo(c) z.LineTo(cx, cy)
} }
// CubeTo adds a cubic Bézier segment, from the pen via b and c to d, and moves // CubeTo adds a cubic Bézier segment, from the pen via (bx, by) and (cx, cy)
// the pen to d. // to (dx, dy), and moves the pen to (dx, dy).
// //
// The coordinates are allowed to be out of the Rasterizer's bounds. // The coordinates are allowed to be out of the Rasterizer's bounds.
func (z *Rasterizer) CubeTo(b, c, d f32.Vec2) { func (z *Rasterizer) CubeTo(bx, by, cx, cy, dx, dy float32) {
a := z.pen ax, ay := z.penX, z.penY
devsq := devSquared(a, b, d) devsq := devSquared(ax, ay, bx, by, dx, dy)
if devsqAlt := devSquared(a, c, d); devsq < devsqAlt { if devsqAlt := devSquared(ax, ay, cx, cy, dx, dy); devsq < devsqAlt {
devsq = devsqAlt devsq = devsqAlt
} }
if devsq >= 0.333 { if devsq >= 0.333 {
@ -234,19 +229,20 @@ func (z *Rasterizer) CubeTo(b, c, d f32.Vec2) {
t, nInv := float32(0), 1/float32(n) t, nInv := float32(0), 1/float32(n)
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
t += nInv t += nInv
ab := lerp(t, a, b) abx, aby := lerp(t, ax, ay, bx, by)
bc := lerp(t, b, c) bcx, bcy := lerp(t, bx, by, cx, cy)
cd := lerp(t, c, d) cdx, cdy := lerp(t, cx, cy, dx, dy)
abc := lerp(t, ab, bc) abcx, abcy := lerp(t, abx, aby, bcx, bcy)
bcd := lerp(t, bc, cd) bcdx, bcdy := lerp(t, bcx, bcy, cdx, cdy)
z.LineTo(lerp(t, abc, bcd)) z.LineTo(lerp(t, abcx, abcy, bcdx, bcdy))
} }
} }
z.LineTo(d) z.LineTo(dx, dy)
} }
// devSquared returns a measure of how curvy the sequnce a to b to c is. It // devSquared returns a measure of how curvy the sequence (ax, ay) to (bx, by)
// determines how many line segments will approximate a Bézier curve segment. // to (cx, cy) is. It determines how many line segments will approximate a
// Bézier curve segment.
// //
// http://lists.nongnu.org/archive/html/freetype-devel/2016-08/msg00080.html // http://lists.nongnu.org/archive/html/freetype-devel/2016-08/msg00080.html
// gives the rationale for this evenly spaced heuristic instead of a recursive // gives the rationale for this evenly spaced heuristic instead of a recursive
@ -258,9 +254,9 @@ func (z *Rasterizer) CubeTo(b, c, d f32.Vec2) {
// Taking a circular arc as a simplifying assumption (ie a spherical cow), // Taking a circular arc as a simplifying assumption (ie a spherical cow),
// where I get n, a recursive approach would get 2^⌈lg n⌉, which, if I haven't // where I get n, a recursive approach would get 2^⌈lg n⌉, which, if I haven't
// made any horrible mistakes, is expected to be 33% more in the limit. // made any horrible mistakes, is expected to be 33% more in the limit.
func devSquared(a, b, c f32.Vec2) float32 { func devSquared(ax, ay, bx, by, cx, cy float32) float32 {
devx := a[0] - 2*b[0] + c[0] devx := ax - 2*bx + cx
devy := a[1] - 2*b[1] + c[1] devy := ay - 2*by + cy
return devx*devx + devy*devy return devx*devx + devy*devy
} }

View File

@ -17,8 +17,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"golang.org/x/image/math/f32"
) )
// encodePNG is useful for manually debugging the tests. // encodePNG is useful for manually debugging the tests.
@ -35,15 +33,13 @@ func encodePNG(dstFilename string, src image.Image) error {
return closeErr return closeErr
} }
func pointOnCircle(center, radius, index, number int) f32.Vec2 { func pointOnCircle(center, radius, index, number int) (x, y float32) {
c := float64(center) c := float64(center)
r := float64(radius) r := float64(radius)
i := float64(index) i := float64(index)
n := float64(number) n := float64(number)
return f32.Vec2{ return float32(c + r*(math.Cos(2*math.Pi*i/n))),
float32(c + r*(math.Cos(2*math.Pi*i/n))), float32(c + r*(math.Sin(2*math.Pi*i/n)))
float32(c + r*(math.Sin(2*math.Pi*i/n))),
}
} }
func TestRasterizeOutOfBounds(t *testing.T) { func TestRasterizeOutOfBounds(t *testing.T) {
@ -59,15 +55,15 @@ func TestRasterizeOutOfBounds(t *testing.T) {
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
for j := 1; j < n/2; j++ { for j := 1; j < n/2; j++ {
z.Reset(2*center, 2*center) z.Reset(2*center, 2*center)
z.MoveTo(f32.Vec2{1 * center, 1 * center}) z.MoveTo(1*center, 1*center)
z.LineTo(pointOnCircle(center, radius, i+0, n)) z.LineTo(pointOnCircle(center, radius, i+0, n))
z.LineTo(pointOnCircle(center, radius, i+j, n)) z.LineTo(pointOnCircle(center, radius, i+j, n))
z.ClosePath() z.ClosePath()
z.MoveTo(f32.Vec2{0 * center, 0 * center}) z.MoveTo(0*center, 0*center)
z.LineTo(f32.Vec2{0 * center, 2 * center}) z.LineTo(0*center, 2*center)
z.LineTo(f32.Vec2{2 * center, 2 * center}) z.LineTo(2*center, 2*center)
z.LineTo(f32.Vec2{2 * center, 0 * center}) z.LineTo(2*center, 0*center)
z.ClosePath() z.ClosePath()
dst := image.NewAlpha(z.Bounds()) dst := image.NewAlpha(z.Bounds())
@ -91,10 +87,7 @@ func TestRasterizePolygon(t *testing.T) {
for radius := 4; radius <= 256; radius *= 2 { for radius := 4; radius <= 256; radius *= 2 {
for n := 3; n <= 19; n += 4 { for n := 3; n <= 19; n += 4 {
z.Reset(2*radius, 2*radius) z.Reset(2*radius, 2*radius)
z.MoveTo(f32.Vec2{ z.MoveTo(float32(2*radius), float32(1*radius))
float32(2 * radius),
float32(1 * radius),
})
for i := 1; i < n; i++ { for i := 1; i < n; i++ {
z.LineTo(pointOnCircle(radius, radius, i, n)) z.LineTo(pointOnCircle(radius, radius, i, n))
} }
@ -112,10 +105,10 @@ func TestRasterizePolygon(t *testing.T) {
func TestRasterizeAlmostAxisAligned(t *testing.T) { func TestRasterizeAlmostAxisAligned(t *testing.T) {
z := NewRasterizer(8, 8) z := NewRasterizer(8, 8)
z.MoveTo(f32.Vec2{2, 2}) z.MoveTo(2, 2)
z.LineTo(f32.Vec2{6, math.Nextafter32(2, 0)}) z.LineTo(6, math.Nextafter32(2, 0))
z.LineTo(f32.Vec2{6, 6}) z.LineTo(6, 6)
z.LineTo(f32.Vec2{math.Nextafter32(2, 0), 6}) z.LineTo(math.Nextafter32(2, 0), 6)
z.ClosePath() z.ClosePath()
dst := image.NewAlpha(z.Bounds()) dst := image.NewAlpha(z.Bounds())
@ -132,10 +125,10 @@ func TestRasterizeWideAlmostHorizontalLines(t *testing.T) {
x := float32(int(1 << i)) x := float32(int(1 << i))
z.Reset(8, 8) z.Reset(8, 8)
z.MoveTo(f32.Vec2{-x, 3}) z.MoveTo(-x, 3)
z.LineTo(f32.Vec2{+x, 4}) z.LineTo(+x, 4)
z.LineTo(f32.Vec2{+x, 6}) z.LineTo(+x, 6)
z.LineTo(f32.Vec2{-x, 6}) z.LineTo(-x, 6)
z.ClosePath() z.ClosePath()
dst := image.NewAlpha(z.Bounds()) dst := image.NewAlpha(z.Bounds())
@ -149,9 +142,9 @@ func TestRasterizeWideAlmostHorizontalLines(t *testing.T) {
func TestRasterize30Degrees(t *testing.T) { func TestRasterize30Degrees(t *testing.T) {
z := NewRasterizer(8, 8) z := NewRasterizer(8, 8)
z.MoveTo(f32.Vec2{4, 4}) z.MoveTo(4, 4)
z.LineTo(f32.Vec2{8, 4}) z.LineTo(8, 4)
z.LineTo(f32.Vec2{4, 6}) z.LineTo(4, 6)
z.ClosePath() z.ClosePath()
dst := image.NewAlpha(z.Bounds()) dst := image.NewAlpha(z.Bounds())
@ -168,11 +161,11 @@ func TestRasterizeRandomLineTos(t *testing.T) {
n, rng := 0, rand.New(rand.NewSource(int64(i))) n, rng := 0, rand.New(rand.NewSource(int64(i)))
z.Reset(i+2, i+2) z.Reset(i+2, i+2)
z.MoveTo(f32.Vec2{float32(i / 2), float32(i / 2)}) z.MoveTo(float32(i/2), float32(i/2))
for ; rng.Intn(16) != 0; n++ { for ; rng.Intn(16) != 0; n++ {
x := 1 + rng.Intn(i) x := 1 + rng.Intn(i)
y := 1 + rng.Intn(i) y := 1 + rng.Intn(i)
z.LineTo(f32.Vec2{float32(x), float32(y)}) z.LineTo(float32(x), float32(y))
} }
z.ClosePath() z.ClosePath()
@ -235,10 +228,10 @@ var basicMask = []byte{
func testBasicPath(t *testing.T, prefix string, dst draw.Image, src image.Image, op draw.Op, want []byte) { 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(2, 2)
z.LineTo(f32.Vec2{8, 2}) z.LineTo(8, 2)
z.QuadTo(f32.Vec2{14, 2}, f32.Vec2{14, 14}) z.QuadTo(14, 2, 14, 14)
z.CubeTo(f32.Vec2{8, 2}, f32.Vec2{5, 20}, f32.Vec2{2, 8}) z.CubeTo(8, 2, 5, 20, 2, 8)
z.ClosePath() z.ClosePath()
z.DrawOp = op z.DrawOp = op
@ -365,45 +358,47 @@ const (
type benchmarkGlyphDatum struct { type benchmarkGlyphDatum struct {
// n being 0, 1 or 2 means moveTo, lineTo or quadTo. // n being 0, 1 or 2 means moveTo, lineTo or quadTo.
n uint32 n uint32
p f32.Vec2 px float32
q f32.Vec2 py float32
qx float32
qy float32
} }
// benchmarkGlyphData is the 'a' glyph from the Roboto Regular font, translated // benchmarkGlyphData is the 'a' glyph from the Roboto Regular font, translated
// so that its top left corner is (0, 0). // so that its top left corner is (0, 0).
var benchmarkGlyphData = []benchmarkGlyphDatum{ var benchmarkGlyphData = []benchmarkGlyphDatum{
{0, f32.Vec2{699, 1102}, f32.Vec2{0, 0}}, {0, 699, 1102, 0, 0},
{2, f32.Vec2{683, 1070}, f32.Vec2{673, 988}}, {2, 683, 1070, 673, 988},
{2, f32.Vec2{544, 1122}, f32.Vec2{365, 1122}}, {2, 544, 1122, 365, 1122},
{2, f32.Vec2{205, 1122}, f32.Vec2{102.5, 1031.5}}, {2, 205, 1122, 102.5, 1031.5},
{2, f32.Vec2{0, 941}, f32.Vec2{0, 802}}, {2, 0, 941, 0, 802},
{2, f32.Vec2{0, 633}, f32.Vec2{128.5, 539.5}}, {2, 0, 633, 128.5, 539.5},
{2, f32.Vec2{257, 446}, f32.Vec2{490, 446}}, {2, 257, 446, 490, 446},
{1, f32.Vec2{670, 446}, f32.Vec2{0, 0}}, {1, 670, 446, 0, 0},
{1, f32.Vec2{670, 361}, f32.Vec2{0, 0}}, {1, 670, 361, 0, 0},
{2, f32.Vec2{670, 264}, f32.Vec2{612, 206.5}}, {2, 670, 264, 612, 206.5},
{2, f32.Vec2{554, 149}, f32.Vec2{441, 149}}, {2, 554, 149, 441, 149},
{2, f32.Vec2{342, 149}, f32.Vec2{275, 199}}, {2, 342, 149, 275, 199},
{2, f32.Vec2{208, 249}, f32.Vec2{208, 320}}, {2, 208, 249, 208, 320},
{1, f32.Vec2{22, 320}, f32.Vec2{0, 0}}, {1, 22, 320, 0, 0},
{2, f32.Vec2{22, 239}, f32.Vec2{79.5, 163.5}}, {2, 22, 239, 79.5, 163.5},
{2, f32.Vec2{137, 88}, f32.Vec2{235.5, 44}}, {2, 137, 88, 235.5, 44},
{2, f32.Vec2{334, 0}, f32.Vec2{452, 0}}, {2, 334, 0, 452, 0},
{2, f32.Vec2{639, 0}, f32.Vec2{745, 93.5}}, {2, 639, 0, 745, 93.5},
{2, f32.Vec2{851, 187}, f32.Vec2{855, 351}}, {2, 851, 187, 855, 351},
{1, f32.Vec2{855, 849}, f32.Vec2{0, 0}}, {1, 855, 849, 0, 0},
{2, f32.Vec2{855, 998}, f32.Vec2{893, 1086}}, {2, 855, 998, 893, 1086},
{1, f32.Vec2{893, 1102}, f32.Vec2{0, 0}}, {1, 893, 1102, 0, 0},
{1, f32.Vec2{699, 1102}, f32.Vec2{0, 0}}, {1, 699, 1102, 0, 0},
{0, f32.Vec2{392, 961}, f32.Vec2{0, 0}}, {0, 392, 961, 0, 0},
{2, f32.Vec2{479, 961}, f32.Vec2{557, 916}}, {2, 479, 961, 557, 916},
{2, f32.Vec2{635, 871}, f32.Vec2{670, 799}}, {2, 635, 871, 670, 799},
{1, f32.Vec2{670, 577}, f32.Vec2{0, 0}}, {1, 670, 577, 0, 0},
{1, f32.Vec2{525, 577}, f32.Vec2{0, 0}}, {1, 525, 577, 0, 0},
{2, f32.Vec2{185, 577}, f32.Vec2{185, 776}}, {2, 185, 577, 185, 776},
{2, f32.Vec2{185, 863}, f32.Vec2{243, 912}}, {2, 185, 863, 243, 912},
{2, f32.Vec2{301, 961}, f32.Vec2{392, 961}}, {2, 301, 961, 392, 961},
} }
func scaledBenchmarkGlyphData(height int) (width int, data []benchmarkGlyphDatum) { func scaledBenchmarkGlyphData(height int) (width int, data []benchmarkGlyphDatum) {
@ -412,10 +407,10 @@ func scaledBenchmarkGlyphData(height int) (width int, data []benchmarkGlyphDatum
// Clone the benchmarkGlyphData slice and scale its coordinates. // Clone the benchmarkGlyphData slice and scale its coordinates.
data = append(data, benchmarkGlyphData...) data = append(data, benchmarkGlyphData...)
for i := range data { for i := range data {
data[i].p[0] *= scale data[i].px *= scale
data[i].p[1] *= scale data[i].py *= scale
data[i].q[0] *= scale data[i].qx *= scale
data[i].q[1] *= scale data[i].qy *= scale
} }
return int(math.Ceil(float64(benchmarkGlyphWidth * scale))), data return int(math.Ceil(float64(benchmarkGlyphWidth * scale))), data
@ -457,11 +452,11 @@ func benchGlyph(b *testing.B, colorModel byte, loose bool, height int, op draw.O
for _, d := range data { for _, d := range data {
switch d.n { switch d.n {
case 0: case 0:
z.MoveTo(d.p) z.MoveTo(d.px, d.py)
case 1: case 1:
z.LineTo(d.p) z.LineTo(d.px, d.py)
case 2: case 2:
z.QuadTo(d.p, d.q) z.QuadTo(d.px, d.py, d.qx, d.qy)
} }
} }
z.Draw(dst, bounds, src, image.Point{}) z.Draw(dst, bounds, src, image.Point{})