Use the fixed.Int26_6 and fixed.Point26_6 types.

This commit is contained in:
Nigel Tao 2015-08-17 10:26:09 +10:00
parent d91c1d6844
commit 856a70c395
7 changed files with 298 additions and 306 deletions

View file

@ -20,12 +20,13 @@ import (
"os"
"github.com/golang/freetype/raster"
"golang.org/x/image/math/fixed"
)
func p(x, y int) raster.Point {
return raster.Point{
X: raster.Fix32(x * 256),
Y: raster.Fix32(y * 256),
func p(x, y int) fixed.Point26_6 {
return fixed.Point26_6{
X: fixed.Int26_6(x * 64),
Y: fixed.Int26_6(y * 64),
}
}

View file

@ -21,6 +21,7 @@ import (
"os"
"github.com/golang/freetype/raster"
"golang.org/x/image/math/fixed"
)
type node struct {
@ -87,11 +88,11 @@ var inside = []node{
node{686, 1274, -1},
}
func p(n node) raster.Point {
func p(n node) fixed.Point26_6 {
x, y := 20+n.x/4, 380-n.y/4
return raster.Point{
X: raster.Fix32(x * 256),
Y: raster.Fix32(y * 256),
return fixed.Point26_6{
X: fixed.Int26_6(x << 6),
Y: fixed.Int26_6(y << 6),
}
}
@ -121,7 +122,7 @@ func contour(r *raster.Rasterizer, ns []node) {
func showNodes(m *image.RGBA, ns []node) {
for _, n := range ns {
p := p(n)
x, y := int(p.X)/256, int(p.Y)/256
x, y := int(p.X)/64, int(p.Y)/64
if !(image.Point{x, y}).In(m.Bounds()) {
continue
}

View file

@ -27,22 +27,23 @@ import (
"os"
"github.com/golang/freetype/raster"
"golang.org/x/image/math/fixed"
)
// pDot returns the dot product p·q.
func pDot(p, q raster.Point) raster.Fix64 {
func pDot(p, q fixed.Point26_6) fixed.Int52_12 {
px, py := int64(p.X), int64(p.Y)
qx, qy := int64(q.X), int64(q.Y)
return raster.Fix64(px*qx + py*qy)
return fixed.Int52_12(px*qx + py*qy)
}
func main() {
const (
n = 17
r = 256 * 80
r = 64 * 80
)
s := raster.Fix32(r * math.Sqrt(2) / 2)
t := raster.Fix32(r * math.Tan(math.Pi/8))
s := fixed.Int26_6(r * math.Sqrt(2) / 2)
t := fixed.Int26_6(r * math.Tan(math.Pi/8))
m := image.NewRGBA(image.Rect(0, 0, 800, 600))
draw.Draw(m, m.Bounds(), image.NewUniform(color.RGBA{63, 63, 63, 255}), image.ZP, draw.Src)
@ -51,38 +52,38 @@ func main() {
z := raster.NewRasterizer(800, 600)
for i := 0; i < n; i++ {
cx := raster.Fix32(25600 + 51200*(i%4))
cy := raster.Fix32(2560 + 32000*(i/4))
c := raster.Point{X: cx, Y: cy}
cx := fixed.Int26_6(6400 + 12800*(i%4))
cy := fixed.Int26_6(640 + 8000*(i/4))
c := fixed.Point26_6{X: cx, Y: cy}
theta := math.Pi * (0.5 + 0.5*float64(i)/(n-1))
dx := raster.Fix32(r * math.Cos(theta))
dy := raster.Fix32(r * math.Sin(theta))
d := raster.Point{X: dx, Y: dy}
dx := fixed.Int26_6(r * math.Cos(theta))
dy := fixed.Int26_6(r * math.Sin(theta))
d := fixed.Point26_6{X: dx, Y: dy}
// Draw a quarter-circle approximated by two quadratic segments,
// with each segment spanning 45 degrees.
z.Start(c)
z.Add1(c.Add(raster.Point{X: r, Y: 0}))
z.Add2(c.Add(raster.Point{X: r, Y: t}), c.Add(raster.Point{X: s, Y: s}))
z.Add2(c.Add(raster.Point{X: t, Y: r}), c.Add(raster.Point{X: 0, Y: r}))
// Add another quadratic segment whose angle ranges between 0 and 90 degrees.
// For an explanation of the magic constants 22, 150, 181 and 256, read the
// comments in the freetype/raster package.
dot := 256 * pDot(d, raster.Point{X: 0, Y: r}) / (r * r)
multiple := raster.Fix32(150 - 22*(dot-181)/(256-181))
z.Add2(c.Add(raster.Point{X: dx, Y: r + dy}.Mul(multiple)), c.Add(d))
z.Add1(c.Add(fixed.Point26_6{X: r, Y: 0}))
z.Add2(c.Add(fixed.Point26_6{X: r, Y: t}), c.Add(fixed.Point26_6{X: s, Y: s}))
z.Add2(c.Add(fixed.Point26_6{X: t, Y: r}), c.Add(fixed.Point26_6{X: 0, Y: r}))
// Add another quadratic segment whose angle ranges between 0 and 90
// degrees. For an explanation of the magic constants 128, 150, 181 and
// 256, read the comments in the freetype/raster package.
dot := 256 * pDot(d, fixed.Point26_6{X: 0, Y: r}) / (r * r)
multiple := fixed.Int26_6(150-(150-128)*(dot-181)/(256-181)) >> 2
z.Add2(c.Add(fixed.Point26_6{X: dx, Y: r + dy}.Mul(multiple)), c.Add(d))
// Close the curve.
z.Add1(c)
}
z.Rasterize(mp)
for i := 0; i < n; i++ {
cx := raster.Fix32(25600 + 51200*(i%4))
cy := raster.Fix32(2560 + 32000*(i/4))
cx := fixed.Int26_6(6400 + 12800*(i%4))
cy := fixed.Int26_6(640 + 8000*(i/4))
for j := 0; j < n; j++ {
theta := math.Pi * float64(j) / (n - 1)
dx := raster.Fix32(r * math.Cos(theta))
dy := raster.Fix32(r * math.Sin(theta))
m.Set(int((cx+dx)/256), int((cy+dy)/256), color.RGBA{255, 255, 0, 255})
dx := fixed.Int26_6(r * math.Cos(theta))
dy := fixed.Int26_6(r * math.Sin(theta))
m.Set(int((cx+dx)/64), int((cy+dy)/64), color.RGBA{255, 255, 0, 255})
}
}

View file

@ -15,6 +15,7 @@ import (
"github.com/golang/freetype/raster"
"github.com/golang/freetype/truetype"
"golang.org/x/image/math/fixed"
)
// These constants determine the size of the glyph cache. The cache is keyed
@ -33,7 +34,7 @@ const (
type cacheEntry struct {
valid bool
glyph truetype.Index
advanceWidth raster.Fix32
advanceWidth fixed.Int26_6
mask *image.Alpha
offset image.Point
}
@ -45,12 +46,12 @@ func ParseFont(b []byte) (*truetype.Font, error) {
return truetype.Parse(b)
}
// Pt converts from a co-ordinate pair measured in pixels to a raster.Point
// co-ordinate pair measured in raster.Fix32 units.
func Pt(x, y int) raster.Point {
return raster.Point{
X: raster.Fix32(x << 8),
Y: raster.Fix32(y << 8),
// Pt converts from a co-ordinate pair measured in pixels to a fixed.Point26_6
// co-ordinate pair measured in fixed.Int26_6 units.
func Pt(x, y int) fixed.Point26_6 {
return fixed.Point26_6{
X: fixed.Int26_6(x << 6),
Y: fixed.Int26_6(y << 6),
}
}
@ -85,12 +86,12 @@ type Context struct {
// PointToFix32 converts the given number of points (as in ``a 12 point font'')
// into fixed point units.
func (c *Context) PointToFix32(x float64) raster.Fix32 {
return raster.Fix32(x * float64(c.dpi) * (256.0 / 72.0))
func (c *Context) PointToFix32(x float64) fixed.Int26_6 {
return fixed.Int26_6(x * float64(c.dpi) * (64.0 / 72.0))
}
// drawContour draws the given closed contour with the given offset.
func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) {
func (c *Context) drawContour(ps []truetype.Point, dx, dy fixed.Int26_6) {
if len(ps) == 0 {
return
}
@ -105,23 +106,23 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) {
// ps[0] is a truetype.Point measured in FUnits and positive Y going
// upwards. start is the same thing measured in fixed point units and
// positive Y going downwards, and offset by (dx, dy).
start := raster.Point{
X: dx + raster.Fix32(ps[0].X<<2),
Y: dy - raster.Fix32(ps[0].Y<<2),
start := fixed.Point26_6{
X: dx + fixed.Int26_6(ps[0].X),
Y: dy - fixed.Int26_6(ps[0].Y),
}
others := []truetype.Point(nil)
if ps[0].Flags&0x01 != 0 {
others = ps[1:]
} else {
last := raster.Point{
X: dx + raster.Fix32(ps[len(ps)-1].X<<2),
Y: dy - raster.Fix32(ps[len(ps)-1].Y<<2),
last := fixed.Point26_6{
X: dx + fixed.Int26_6(ps[len(ps)-1].X),
Y: dy - fixed.Int26_6(ps[len(ps)-1].Y),
}
if ps[len(ps)-1].Flags&0x01 != 0 {
start = last
others = ps[:len(ps)-1]
} else {
start = raster.Point{
start = fixed.Point26_6{
X: (start.X + last.X) / 2,
Y: (start.Y + last.Y) / 2,
}
@ -131,9 +132,9 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) {
c.r.Start(start)
q0, on0 := start, true
for _, p := range others {
q := raster.Point{
X: dx + raster.Fix32(p.X<<2),
Y: dy - raster.Fix32(p.Y<<2),
q := fixed.Point26_6{
X: dx + fixed.Int26_6(p.X),
Y: dy - fixed.Int26_6(p.Y),
}
on := p.Flags&0x01 != 0
if on {
@ -146,7 +147,7 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) {
if on0 {
// No-op.
} else {
mid := raster.Point{
mid := fixed.Point26_6{
X: (q0.X + q.X) / 2,
Y: (q0.Y + q.Y) / 2,
}
@ -166,27 +167,27 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) {
// rasterize returns the advance width, glyph mask and integer-pixel offset
// to render the given glyph at the given sub-pixel offsets.
// The 24.8 fixed point arguments fx and fy must be in the range [0, 1).
func (c *Context) rasterize(glyph truetype.Index, fx, fy raster.Fix32) (
raster.Fix32, *image.Alpha, image.Point, error) {
func (c *Context) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) (
fixed.Int26_6, *image.Alpha, image.Point, error) {
if err := c.glyphBuf.Load(c.font, c.scale, glyph, truetype.Hinting(c.hinting)); err != nil {
return 0, nil, image.Point{}, err
}
// Calculate the integer-pixel bounds for the glyph.
xmin := int(fx+raster.Fix32(c.glyphBuf.B.XMin<<2)) >> 8
ymin := int(fy-raster.Fix32(c.glyphBuf.B.YMax<<2)) >> 8
xmax := int(fx+raster.Fix32(c.glyphBuf.B.XMax<<2)+0xff) >> 8
ymax := int(fy-raster.Fix32(c.glyphBuf.B.YMin<<2)+0xff) >> 8
xmin := int(fx+fixed.Int26_6(c.glyphBuf.B.XMin)) >> 6
ymin := int(fy-fixed.Int26_6(c.glyphBuf.B.YMax)) >> 6
xmax := int(fx+fixed.Int26_6(c.glyphBuf.B.XMax)+0xff) >> 6
ymax := int(fy-fixed.Int26_6(c.glyphBuf.B.YMin)+0xff) >> 6
if xmin > xmax || ymin > ymax {
return 0, nil, image.Point{}, errors.New("freetype: negative sized glyph")
}
// A TrueType's glyph's nodes can have negative co-ordinates, but the
// rasterizer clips anything left of x=0 or above y=0. xmin and ymin
// are the pixel offsets, based on the font's FUnit metrics, that let
// a negative co-ordinate in TrueType space be non-negative in
// rasterizer space. xmin and ymin are typically <= 0.
fx += raster.Fix32(-xmin << 8)
fy += raster.Fix32(-ymin << 8)
// rasterizer clips anything left of x=0 or above y=0. xmin and ymin are
// the pixel offsets, based on the font's FUnit metrics, that let a
// negative co-ordinate in TrueType space be non-negative in rasterizer
// space. xmin and ymin are typically <= 0.
fx += fixed.Int26_6(-xmin << 6)
fy += fixed.Int26_6(-ymin << 6)
// Rasterize the glyph's vectors.
c.r.Clear()
e0 := 0
@ -196,23 +197,23 @@ func (c *Context) rasterize(glyph truetype.Index, fx, fy raster.Fix32) (
}
a := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin))
c.r.Rasterize(raster.NewAlphaSrcPainter(a))
return raster.Fix32(c.glyphBuf.AdvanceWidth << 2), a, image.Point{xmin, ymin}, nil
return fixed.Int26_6(c.glyphBuf.AdvanceWidth), a, image.Point{xmin, ymin}, nil
}
// glyph returns the advance width, glyph mask and integer-pixel offset to
// render the given glyph at the given sub-pixel point. It is a cache for the
// rasterize method. Unlike rasterize, p's co-ordinates do not have to be in
// the range [0, 1).
func (c *Context) glyph(glyph truetype.Index, p raster.Point) (
raster.Fix32, *image.Alpha, image.Point, error) {
func (c *Context) glyph(glyph truetype.Index, p fixed.Point26_6) (
fixed.Int26_6, *image.Alpha, image.Point, error) {
// Split p.X and p.Y into their integer and fractional parts.
ix, fx := int(p.X>>8), p.X&0xff
iy, fy := int(p.Y>>8), p.Y&0xff
ix, fx := int(p.X>>6), p.X&0x3f
iy, fy := int(p.Y>>6), p.Y&0x3f
// Calculate the index t into the cache array.
tg := int(glyph) % nGlyphs
tx := int(fx) / (256 / nXFractions)
ty := int(fy) / (256 / nYFractions)
tx := int(fx) / (64 / nXFractions)
ty := int(fy) / (64 / nYFractions)
t := ((tg*nXFractions)+tx)*nYFractions + ty
// Check for a cache hit.
if e := c.cache[t]; e.valid && e.glyph == glyph {
@ -233,24 +234,25 @@ func (c *Context) glyph(glyph truetype.Index, p raster.Point) (
// above and to the right of the point, but some may be below or to the left.
// For example, drawing a string that starts with a 'J' in an italic font may
// affect pixels below and left of the point.
// p is a raster.Point and can therefore represent sub-pixel positions.
func (c *Context) DrawString(s string, p raster.Point) (raster.Point, error) {
//
// p is a fixed.Point26_6 and can therefore represent sub-pixel positions.
func (c *Context) DrawString(s string, p fixed.Point26_6) (fixed.Point26_6, error) {
if c.font == nil {
return raster.Point{}, errors.New("freetype: DrawText called with a nil font")
return fixed.Point26_6{}, errors.New("freetype: DrawText called with a nil font")
}
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := c.font.Index(rune)
if hasPrev {
kern := raster.Fix32(c.font.Kerning(c.scale, prev, index)) << 2
kern := fixed.Int26_6(c.font.Kerning(c.scale, prev, index))
if c.hinting != NoHinting {
kern = (kern + 128) &^ 255
kern = (kern + 32) &^ 63
}
p.X += kern
}
advanceWidth, mask, offset, err := c.glyph(index, p)
if err != nil {
return raster.Point{}, err
return fixed.Point26_6{}, err
}
p.X += advanceWidth
glyphRect := mask.Bounds().Add(offset)

View file

@ -8,36 +8,12 @@ package raster
import (
"fmt"
"math"
"golang.org/x/image/math/fixed"
)
// A Fix32 is a 24.8 fixed point number.
type Fix32 int32
// A Fix64 is a 48.16 fixed point number.
type Fix64 int64
// String returns a human-readable representation of a 24.8 fixed point number.
// For example, the number one-and-a-quarter becomes "1:064".
func (x Fix32) String() string {
if x < 0 {
x = -x
return fmt.Sprintf("-%d:%03d", int32(x/256), int32(x%256))
}
return fmt.Sprintf("%d:%03d", int32(x/256), int32(x%256))
}
// String returns a human-readable representation of a 48.16 fixed point number.
// For example, the number one-and-a-quarter becomes "1:16384".
func (x Fix64) String() string {
if x < 0 {
x = -x
return fmt.Sprintf("-%d:%05d", int64(x/65536), int64(x%65536))
}
return fmt.Sprintf("%d:%05d", int64(x/65536), int64(x%65536))
}
// maxAbs returns the maximum of abs(a) and abs(b).
func maxAbs(a, b Fix32) Fix32 {
func maxAbs(a, b fixed.Int26_6) fixed.Int26_6 {
if a < 0 {
a = -a
}
@ -50,138 +26,112 @@ func maxAbs(a, b Fix32) Fix32 {
return a
}
// A Point represents a two-dimensional point or vector, in 24.8 fixed point
// format.
type Point struct {
X, Y Fix32
}
// String returns a human-readable representation of a Point.
func (p Point) String() string {
return "(" + p.X.String() + ", " + p.Y.String() + ")"
}
// Add returns the vector p + q.
func (p Point) Add(q Point) Point {
return Point{p.X + q.X, p.Y + q.Y}
}
// Sub returns the vector p - q.
func (p Point) Sub(q Point) Point {
return Point{p.X - q.X, p.Y - q.Y}
}
// Mul returns the vector k * p.
func (p Point) Mul(k Fix32) Point {
return Point{p.X * k / 256, p.Y * k / 256}
}
// pNeg returns the vector -p, or equivalently p rotated by 180 degrees.
func pNeg(p Point) Point {
return Point{-p.X, -p.Y}
func pNeg(p fixed.Point26_6) fixed.Point26_6 {
return fixed.Point26_6{-p.X, -p.Y}
}
// pDot returns the dot product p·q.
func pDot(p, q Point) Fix64 {
func pDot(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 {
px, py := int64(p.X), int64(p.Y)
qx, qy := int64(q.X), int64(q.Y)
return Fix64(px*qx + py*qy)
return fixed.Int52_12(px*qx + py*qy)
}
// pLen returns the length of the vector p.
func pLen(p Point) Fix32 {
func pLen(p fixed.Point26_6) fixed.Int26_6 {
// TODO(nigeltao): use fixed point math.
x := float64(p.X)
y := float64(p.Y)
return Fix32(math.Sqrt(x*x + y*y))
return fixed.Int26_6(math.Sqrt(x*x + y*y))
}
// pNorm returns the vector p normalized to the given length, or zero if p is
// degenerate.
func pNorm(p Point, length Fix32) Point {
func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 {
d := pLen(p)
if d == 0 {
return Point{}
return fixed.Point26_6{}
}
s, t := int64(length), int64(d)
x := int64(p.X) * s / t
y := int64(p.Y) * s / t
return Point{Fix32(x), Fix32(y)}
return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)}
}
// pRot45CW returns the vector p rotated clockwise by 45 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot45CW is {1/√2, 1/√2}.
func pRot45CW(p Point) Point {
func pRot45CW(p fixed.Point26_6) fixed.Point26_6 {
// 181/256 is approximately 1/√2, or sin(π/4).
px, py := int64(p.X), int64(p.Y)
qx := (+px - py) * 181 / 256
qy := (+px + py) * 181 / 256
return Point{Fix32(qx), Fix32(qy)}
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
}
// pRot90CW returns the vector p rotated clockwise by 90 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot90CW is {0, 1}.
func pRot90CW(p Point) Point {
return Point{-p.Y, p.X}
func pRot90CW(p fixed.Point26_6) fixed.Point26_6 {
return fixed.Point26_6{-p.Y, p.X}
}
// pRot135CW returns the vector p rotated clockwise by 135 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot135CW is {-1/√2, 1/√2}.
func pRot135CW(p Point) Point {
func pRot135CW(p fixed.Point26_6) fixed.Point26_6 {
// 181/256 is approximately 1/√2, or sin(π/4).
px, py := int64(p.X), int64(p.Y)
qx := (-px - py) * 181 / 256
qy := (+px - py) * 181 / 256
return Point{Fix32(qx), Fix32(qy)}
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
}
// pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}.
func pRot45CCW(p Point) Point {
func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 {
// 181/256 is approximately 1/√2, or sin(π/4).
px, py := int64(p.X), int64(p.Y)
qx := (+px + py) * 181 / 256
qy := (-px + py) * 181 / 256
return Point{Fix32(qx), Fix32(qy)}
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
}
// pRot90CCW returns the vector p rotated counter-clockwise by 90 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot90CCW is {0, -1}.
func pRot90CCW(p Point) Point {
return Point{p.Y, -p.X}
func pRot90CCW(p fixed.Point26_6) fixed.Point26_6 {
return fixed.Point26_6{p.Y, -p.X}
}
// pRot135CCW returns the vector p rotated counter-clockwise by 135 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot135CCW is {-1/√2, -1/√2}.
func pRot135CCW(p Point) Point {
func pRot135CCW(p fixed.Point26_6) fixed.Point26_6 {
// 181/256 is approximately 1/√2, or sin(π/4).
px, py := int64(p.X), int64(p.Y)
qx := (-px + py) * 181 / 256
qy := (-px - py) * 181 / 256
return Point{Fix32(qx), Fix32(qy)}
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
}
// An Adder accumulates points on a curve.
type Adder interface {
// Start starts a new curve at the given point.
Start(a Point)
Start(a fixed.Point26_6)
// Add1 adds a linear segment to the current curve.
Add1(b Point)
Add1(b fixed.Point26_6)
// Add2 adds a quadratic segment to the current curve.
Add2(b, c Point)
Add2(b, c fixed.Point26_6)
// Add3 adds a cubic segment to the current curve.
Add3(b, c, d Point)
Add3(b, c, d fixed.Point26_6)
}
// A Path is a sequence of curves, and a curve is a start point followed by a
// sequence of linear, quadratic or cubic segments.
type Path []Fix32
type Path []fixed.Int26_6
// String returns a human-readable representation of a Path.
func (p Path) String() string {
@ -192,16 +142,16 @@ func (p Path) String() string {
}
switch p[i] {
case 0:
s += "S0" + fmt.Sprint([]Fix32(p[i+1:i+3]))
s += "S0" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3]))
i += 4
case 1:
s += "A1" + fmt.Sprint([]Fix32(p[i+1:i+3]))
s += "A1" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3]))
i += 4
case 2:
s += "A2" + fmt.Sprint([]Fix32(p[i+1:i+5]))
s += "A2" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+5]))
i += 6
case 3:
s += "A3" + fmt.Sprint([]Fix32(p[i+1:i+7]))
s += "A3" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+7]))
i += 8
default:
panic("freetype/raster: bad path")
@ -216,22 +166,22 @@ func (p *Path) Clear() {
}
// Start starts a new curve at the given point.
func (p *Path) Start(a Point) {
func (p *Path) Start(a fixed.Point26_6) {
*p = append(*p, 0, a.X, a.Y, 0)
}
// Add1 adds a linear segment to the current curve.
func (p *Path) Add1(b Point) {
func (p *Path) Add1(b fixed.Point26_6) {
*p = append(*p, 1, b.X, b.Y, 1)
}
// Add2 adds a quadratic segment to the current curve.
func (p *Path) Add2(b, c Point) {
func (p *Path) Add2(b, c fixed.Point26_6) {
*p = append(*p, 2, b.X, b.Y, c.X, c.Y, 2)
}
// Add3 adds a cubic segment to the current curve.
func (p *Path) Add3(b, c, d Point) {
func (p *Path) Add3(b, c, d fixed.Point26_6) {
*p = append(*p, 3, b.X, b.Y, c.X, c.Y, d.X, d.Y, 3)
}
@ -241,18 +191,18 @@ func (p *Path) AddPath(q Path) {
}
// AddStroke adds a stroked Path.
func (p *Path) AddStroke(q Path, width Fix32, cr Capper, jr Joiner) {
func (p *Path) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
Stroke(p, q, width, cr, jr)
}
// firstPoint returns the first point in a non-empty Path.
func (p Path) firstPoint() Point {
return Point{p[1], p[2]}
func (p Path) firstPoint() fixed.Point26_6 {
return fixed.Point26_6{p[1], p[2]}
}
// lastPoint returns the last point in a non-empty Path.
func (p Path) lastPoint() Point {
return Point{p[len(p)-3], p[len(p)-2]}
func (p Path) lastPoint() fixed.Point26_6 {
return fixed.Point26_6{p[len(p)-3], p[len(p)-2]}
}
// addPathReversed adds q reversed to p.
@ -272,13 +222,22 @@ func addPathReversed(p Adder, q Path) {
return
case 1:
i -= 4
p.Add1(Point{q[i-2], q[i-1]})
p.Add1(
fixed.Point26_6{q[i-2], q[i-1]},
)
case 2:
i -= 6
p.Add2(Point{q[i+2], q[i+3]}, Point{q[i-2], q[i-1]})
p.Add2(
fixed.Point26_6{q[i+2], q[i+3]},
fixed.Point26_6{q[i-2], q[i-1]},
)
case 3:
i -= 8
p.Add3(Point{q[i+4], q[i+5]}, Point{q[i+2], q[i+3]}, Point{q[i-2], q[i-1]})
p.Add3(
fixed.Point26_6{q[i+4], q[i+5]},
fixed.Point26_6{q[i+2], q[i+3]},
fixed.Point26_6{q[i-2], q[i-1]},
)
default:
panic("freetype/raster: bad path")
}

View file

@ -3,20 +3,22 @@
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
// The raster package provides an anti-aliasing 2-D rasterizer.
// Package raster provides an anti-aliasing 2-D rasterizer.
//
// It is part of the larger Freetype-Go suite of font-related packages,
// but the raster package is not specific to font rasterization, and can
// be used standalone without any other Freetype-Go package.
// It is part of the larger Freetype suite of font-related packages, but the
// raster package is not specific to font rasterization, and can be used
// standalone without any other Freetype package.
//
// Rasterization is done by the same area/coverage accumulation algorithm
// as the Freetype "smooth" module, and the Anti-Grain Geometry library.
// A description of the area/coverage algorithm is at
// Rasterization is done by the same area/coverage accumulation algorithm as
// the Freetype "smooth" module, and the Anti-Grain Geometry library. A
// description of the area/coverage algorithm is at
// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm
package raster // import "github.com/golang/freetype/raster"
import (
"strconv"
"golang.org/x/image/math/fixed"
)
// A cell is part of a linked list (for a given yi co-ordinate) of accumulated
@ -41,7 +43,7 @@ type Rasterizer struct {
splitScale2, splitScale3 int
// The current pen position.
a Point
a fixed.Point26_6
// The current cell and its area/coverage being accumulated.
xi, yi int
area, cover int
@ -116,12 +118,12 @@ func (r *Rasterizer) setCell(xi, yi int) {
// scan accumulates area/coverage for the yi'th scanline, going from
// x0 to x1 in the horizontal direction (in 24.8 fixed point co-ordinates)
// and from y0f to y1f fractional vertical units within that scanline.
func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f Fix32) {
// Break the 24.8 fixed point X co-ordinates into integral and fractional parts.
x0i := int(x0) / 256
x0f := x0 - Fix32(256*x0i)
x1i := int(x1) / 256
x1f := x1 - Fix32(256*x1i)
func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f fixed.Int26_6) {
// Break the 26.6 fixed point X co-ordinates into integral and fractional parts.
x0i := int(x0) / 64
x0f := x0 - fixed.Int26_6(64*x0i)
x1i := int(x1) / 64
x1f := x1 - fixed.Int26_6(64*x1i)
// A perfectly horizontal scan.
if y0f == y1f {
@ -137,17 +139,17 @@ func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f Fix32) {
}
// There are at least two cells. Apart from the first and last cells,
// all intermediate cells go through the full width of the cell,
// or 256 units in 24.8 fixed point format.
// or 64 units in 26.6 fixed point format.
var (
p, q, edge0, edge1 Fix32
p, q, edge0, edge1 fixed.Int26_6
xiDelta int
)
if dx > 0 {
p, q = (256-x0f)*dy, dx
edge0, edge1, xiDelta = 0, 256, 1
p, q = (64-x0f)*dy, dx
edge0, edge1, xiDelta = 0, 64, 1
} else {
p, q = x0f*dy, -dx
edge0, edge1, xiDelta = 256, 0, -1
edge0, edge1, xiDelta = 64, 0, -1
}
yDelta, yRem := p/q, p%q
if yRem < 0 {
@ -162,7 +164,7 @@ func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f Fix32) {
r.setCell(xi, yi)
if xi != x1i {
// Do all the intermediate cells.
p = 256 * (y1f - y + yDelta)
p = 64 * (y1f - y + yDelta)
fullDelta, fullRem := p/q, p%q
if fullRem < 0 {
fullDelta -= 1
@ -176,7 +178,7 @@ func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f Fix32) {
yDelta += 1
yRem -= q
}
r.area += int(256 * yDelta)
r.area += int(64 * yDelta)
r.cover += int(yDelta)
xi, y = xi+xiDelta, y+yDelta
r.setCell(xi, yi)
@ -189,21 +191,22 @@ func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f Fix32) {
}
// Start starts a new curve at the given point.
func (r *Rasterizer) Start(a Point) {
r.setCell(int(a.X/256), int(a.Y/256))
func (r *Rasterizer) Start(a fixed.Point26_6) {
r.setCell(int(a.X/64), int(a.Y/64))
r.a = a
}
// Add1 adds a linear segment to the current curve.
func (r *Rasterizer) Add1(b Point) {
func (r *Rasterizer) Add1(b fixed.Point26_6) {
x0, y0 := r.a.X, r.a.Y
x1, y1 := b.X, b.Y
dx, dy := x1-x0, y1-y0
// Break the 24.8 fixed point Y co-ordinates into integral and fractional parts.
y0i := int(y0) / 256
y0f := y0 - Fix32(256*y0i)
y1i := int(y1) / 256
y1f := y1 - Fix32(256*y1i)
// Break the 26.6 fixed point Y co-ordinates into integral and fractional
// parts.
y0i := int(y0) / 64
y0f := y0 - fixed.Int26_6(64*y0i)
y1i := int(y1) / 64
y1f := y1 - fixed.Int26_6(64*y1i)
if y0i == y1i {
// There is only one scanline.
@ -213,16 +216,16 @@ func (r *Rasterizer) Add1(b Point) {
// This is a vertical line segment. We avoid calling r.scan and instead
// manipulate r.area and r.cover directly.
var (
edge0, edge1 Fix32
edge0, edge1 fixed.Int26_6
yiDelta int
)
if dy > 0 {
edge0, edge1, yiDelta = 0, 256, 1
edge0, edge1, yiDelta = 0, 64, 1
} else {
edge0, edge1, yiDelta = 256, 0, -1
edge0, edge1, yiDelta = 64, 0, -1
}
x0i, yi := int(x0)/256, y0i
x0fTimes2 := (int(x0) - (256 * x0i)) * 2
x0i, yi := int(x0)/64, y0i
x0fTimes2 := (int(x0) - (64 * x0i)) * 2
// Do the first pixel.
dcover := int(edge1 - y0f)
darea := int(x0fTimes2 * dcover)
@ -246,19 +249,19 @@ func (r *Rasterizer) Add1(b Point) {
r.cover += dcover
} else {
// There are at least two scanlines. Apart from the first and last scanlines,
// all intermediate scanlines go through the full height of the row, or 256
// units in 24.8 fixed point format.
// There are at least two scanlines. Apart from the first and last
// scanlines, all intermediate scanlines go through the full height of
// the row, or 64 units in 26.6 fixed point format.
var (
p, q, edge0, edge1 Fix32
p, q, edge0, edge1 fixed.Int26_6
yiDelta int
)
if dy > 0 {
p, q = (256-y0f)*dx, dy
edge0, edge1, yiDelta = 0, 256, 1
p, q = (64-y0f)*dx, dy
edge0, edge1, yiDelta = 0, 64, 1
} else {
p, q = y0f*dx, -dy
edge0, edge1, yiDelta = 256, 0, -1
edge0, edge1, yiDelta = 64, 0, -1
}
xDelta, xRem := p/q, p%q
if xRem < 0 {
@ -269,10 +272,10 @@ func (r *Rasterizer) Add1(b Point) {
x, yi := x0, y0i
r.scan(yi, x, y0f, x+xDelta, edge1)
x, yi = x+xDelta, yi+yiDelta
r.setCell(int(x)/256, yi)
r.setCell(int(x)/64, yi)
if yi != y1i {
// Do all the intermediate scanlines.
p = 256 * dx
p = 64 * dx
fullDelta, fullRem := p/q, p%q
if fullRem < 0 {
fullDelta -= 1
@ -288,7 +291,7 @@ func (r *Rasterizer) Add1(b Point) {
}
r.scan(yi, x, edge0, x+xDelta, edge1)
x, yi = x+xDelta, yi+yiDelta
r.setCell(int(x)/256, yi)
r.setCell(int(x)/64, yi)
}
}
// Do the last scanline.
@ -299,10 +302,11 @@ func (r *Rasterizer) Add1(b Point) {
}
// Add2 adds a quadratic segment to the current curve.
func (r *Rasterizer) Add2(b, c Point) {
// Calculate nSplit (the number of recursive decompositions) based on how `curvy' it is.
// Specifically, how much the middle point b deviates from (a+c)/2.
dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / Fix32(r.splitScale2)
func (r *Rasterizer) Add2(b, c fixed.Point26_6) {
// Calculate nSplit (the number of recursive decompositions) based on how
// `curvy' it is. Specifically, how much the middle point b deviates from
// (a+c)/2.
dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2)
nsplit := 0
for dev > 0 {
dev /= 4
@ -315,7 +319,7 @@ func (r *Rasterizer) Add2(b, c Point) {
}
// Recursively decompose the curve nSplit levels deep.
var (
pStack [2*maxNsplit + 3]Point
pStack [2*maxNsplit + 3]fixed.Point26_6
sStack [maxNsplit + 1]int
i int
)
@ -347,7 +351,7 @@ func (r *Rasterizer) Add2(b, c Point) {
// Replace the level-0 quadratic with a two-linear-piece approximation.
midx := (p[0].X + 2*p[1].X + p[2].X) / 4
midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4
r.Add1(Point{midx, midy})
r.Add1(fixed.Point26_6{midx, midy})
r.Add1(p[0])
i--
}
@ -355,10 +359,10 @@ func (r *Rasterizer) Add2(b, c Point) {
}
// Add3 adds a cubic segment to the current curve.
func (r *Rasterizer) Add3(b, c, d Point) {
func (r *Rasterizer) Add3(b, c, d fixed.Point26_6) {
// Calculate nSplit (the number of recursive decompositions) based on how `curvy' it is.
dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / Fix32(r.splitScale2)
dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / Fix32(r.splitScale3)
dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / fixed.Int26_6(r.splitScale2)
dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / fixed.Int26_6(r.splitScale3)
nsplit := 0
for dev2 > 0 || dev3 > 0 {
dev2 /= 8
@ -372,7 +376,7 @@ func (r *Rasterizer) Add3(b, c, d Point) {
}
// Recursively decompose the curve nSplit levels deep.
var (
pStack [3*maxNsplit + 4]Point
pStack [3*maxNsplit + 4]fixed.Point26_6
sStack [maxNsplit + 1]int
i int
)
@ -413,7 +417,7 @@ func (r *Rasterizer) Add3(b, c, d Point) {
// Replace the level-0 cubic with a two-linear-piece approximation.
midx := (p[0].X + 3*(p[1].X+p[2].X) + p[3].X) / 8
midy := (p[0].Y + 3*(p[1].Y+p[2].Y) + p[3].Y) / 8
r.Add1(Point{midx, midy})
r.Add1(fixed.Point26_6{midx, midy})
r.Add1(p[0])
i--
}
@ -425,16 +429,27 @@ func (r *Rasterizer) AddPath(p Path) {
for i := 0; i < len(p); {
switch p[i] {
case 0:
r.Start(Point{p[i+1], p[i+2]})
r.Start(
fixed.Point26_6{p[i+1], p[i+2]},
)
i += 4
case 1:
r.Add1(Point{p[i+1], p[i+2]})
r.Add1(
fixed.Point26_6{p[i+1], p[i+2]},
)
i += 4
case 2:
r.Add2(Point{p[i+1], p[i+2]}, Point{p[i+3], p[i+4]})
r.Add2(
fixed.Point26_6{p[i+1], p[i+2]},
fixed.Point26_6{p[i+3], p[i+4]},
)
i += 6
case 3:
r.Add3(Point{p[i+1], p[i+2]}, Point{p[i+3], p[i+4]}, Point{p[i+5], p[i+6]})
r.Add3(
fixed.Point26_6{p[i+1], p[i+2]},
fixed.Point26_6{p[i+3], p[i+4]},
fixed.Point26_6{p[i+5], p[i+6]},
)
i += 8
default:
panic("freetype/raster: bad path")
@ -443,43 +458,44 @@ func (r *Rasterizer) AddPath(p Path) {
}
// AddStroke adds a stroked Path.
func (r *Rasterizer) AddStroke(q Path, width Fix32, cr Capper, jr Joiner) {
func (r *Rasterizer) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
Stroke(r, q, width, cr, jr)
}
// Converts an area value to a uint32 alpha value. A completely filled pixel
// corresponds to an area of 256*256*2, and an alpha of 1<<32-1. The
// areaToAlpha converts an area value to a uint32 alpha value. A completely
// filled pixel corresponds to an area of 64*64*2, and an alpha of 1<<32-1. The
// conversion of area values greater than this depends on the winding rule:
// even-odd or non-zero.
func (r *Rasterizer) areaToAlpha(area int) uint32 {
// The C Freetype implementation (version 2.3.12) does "alpha := area>>1" without
// the +1. Round-to-nearest gives a more symmetric result than round-down.
// The C implementation also returns 8-bit alpha, not 32-bit alpha.
// The C Freetype implementation (version 2.3.12) does "alpha := area>>1"
// without the +1. Round-to-nearest gives a more symmetric result than
// round-down. The C implementation also returns 8-bit alpha, not 32-bit
// alpha.
a := (area + 1) >> 1
if a < 0 {
a = -a
}
alpha := uint32(a)
if r.UseNonZeroWinding {
if alpha > 0xffff {
alpha = 0xffff
if alpha > 0x0fff {
alpha = 0x0fff
}
} else {
alpha &= 0x1ffff
if alpha > 0x10000 {
alpha = 0x20000 - alpha
} else if alpha == 0x10000 {
alpha = 0x0ffff
alpha &= 0x1fff
if alpha > 0x1000 {
alpha = 0x2000 - alpha
} else if alpha == 0x1000 {
alpha = 0x0fff
}
}
alpha |= alpha << 16
return alpha
alpha >>= 4
return alpha * 0x01010101
}
// Rasterize converts r's accumulated curves into Spans for p. The Spans
// passed to p are non-overlapping, and sorted by Y and then X. They all
// have non-zero width (and 0 <= X0 < X1 <= r.width) and non-zero A, except
// for the final Span, which has Y, X0, X1 and A all equal to zero.
// Rasterize converts r's accumulated curves into Spans for p. The Spans passed
// to p are non-overlapping, and sorted by Y and then X. They all have non-zero
// width (and 0 <= X0 < X1 <= r.width) and non-zero A, except for the final
// Span, which has Y, X0, X1 and A all equal to zero.
func (r *Rasterizer) Rasterize(p Painter) {
r.saveCell()
s := 0
@ -487,7 +503,7 @@ func (r *Rasterizer) Rasterize(p Painter) {
xi, cover := 0, 0
for c := r.cellIndex[yi]; c != -1; c = r.cell[c].next {
if cover != 0 && r.cell[c].xi > xi {
alpha := r.areaToAlpha(cover * 256 * 2)
alpha := r.areaToAlpha(cover * 64 * 2)
if alpha != 0 {
xi0, xi1 := xi, r.cell[c].xi
if xi0 < 0 {
@ -503,7 +519,7 @@ func (r *Rasterizer) Rasterize(p Painter) {
}
}
cover += r.cell[c].cover
alpha := r.areaToAlpha(cover*256*2 - r.cell[c].area)
alpha := r.areaToAlpha(cover*64*2 - r.cell[c].area)
xi = r.cell[c].xi + 1
if alpha != 0 {
xi0, xi1 := r.cell[c].xi, xi
@ -529,7 +545,7 @@ func (r *Rasterizer) Rasterize(p Painter) {
// Clear cancels any previous calls to r.Start or r.AddXxx.
func (r *Rasterizer) Clear() {
r.a = Point{}
r.a = fixed.Point26_6{}
r.xi = 0
r.yi = 0
r.area = 0
@ -541,7 +557,7 @@ func (r *Rasterizer) Clear() {
}
// SetBounds sets the maximum width and height of the rasterized image and
// calls Clear. The width and height are in pixels, not Fix32 units.
// calls Clear. The width and height are in pixels, not fixed.Int26_6 units.
func (r *Rasterizer) SetBounds(width, height int) {
if width < 0 {
width = 0
@ -549,10 +565,9 @@ func (r *Rasterizer) SetBounds(width, height int) {
if height < 0 {
height = 0
}
// Use the same ssN heuristic as the C Freetype implementation.
// The C implementation uses the values 32, 16, but those are in
// 26.6 fixed point units, and we use 24.8 fixed point everywhere.
ss2, ss3 := 128, 64
// Use the same ssN heuristic as the C Freetype (version 2.4.0)
// implementation.
ss2, ss3 := 32, 16
if width > 24 || height > 24 {
ss2, ss3 = 2*ss2, 2*ss3
if width > 120 || height > 120 {

View file

@ -5,21 +5,25 @@
package raster
import (
"golang.org/x/image/math/fixed"
)
// Two points are considered practically equal if the square of the distance
// between them is less than one quarter (i.e. 16384 / 65536 in Fix64).
const epsilon = 16384
// between them is less than one quarter (i.e. 1024 / 4096).
const epsilon = fixed.Int52_12(1024)
// A Capper signifies how to begin or end a stroked path.
type Capper interface {
// Cap adds a cap to p given a pivot point and the normal vector of a
// terminal segment. The normal's length is half of the stroke width.
Cap(p Adder, halfWidth Fix32, pivot, n1 Point)
Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6)
}
// The CapperFunc type adapts an ordinary function to be a Capper.
type CapperFunc func(Adder, Fix32, Point, Point)
type CapperFunc func(Adder, fixed.Int26_6, fixed.Point26_6, fixed.Point26_6)
func (f CapperFunc) Cap(p Adder, halfWidth Fix32, pivot, n1 Point) {
func (f CapperFunc) Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
f(p, halfWidth, pivot, n1)
}
@ -28,20 +32,20 @@ type Joiner interface {
// Join adds a join to the two sides of a stroked path given a pivot
// point and the normal vectors of the trailing and leading segments.
// Both normals have length equal to half of the stroke width.
Join(lhs, rhs Adder, halfWidth Fix32, pivot, n0, n1 Point)
Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)
}
// The JoinerFunc type adapts an ordinary function to be a Joiner.
type JoinerFunc func(lhs, rhs Adder, halfWidth Fix32, pivot, n0, n1 Point)
type JoinerFunc func(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)
func (f JoinerFunc) Join(lhs, rhs Adder, halfWidth Fix32, pivot, n0, n1 Point) {
func (f JoinerFunc) Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
f(lhs, rhs, halfWidth, pivot, n0, n1)
}
// RoundCapper adds round caps to a stroked path.
var RoundCapper Capper = CapperFunc(roundCapper)
func roundCapper(p Adder, halfWidth Fix32, pivot, n1 Point) {
func roundCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
// The cubic Bézier approximation to a circle involves the magic number
// (√2 - 1) * 4/3, which is approximately 141/256.
const k = 141
@ -56,14 +60,14 @@ func roundCapper(p Adder, halfWidth Fix32, pivot, n1 Point) {
// ButtCapper adds butt caps to a stroked path.
var ButtCapper Capper = CapperFunc(buttCapper)
func buttCapper(p Adder, halfWidth Fix32, pivot, n1 Point) {
func buttCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
p.Add1(pivot.Add(n1))
}
// SquareCapper adds square caps to a stroked path.
var SquareCapper Capper = CapperFunc(squareCapper)
func squareCapper(p Adder, halfWidth Fix32, pivot, n1 Point) {
func squareCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
e := pRot90CCW(n1)
side := pivot.Add(e)
p.Add1(side.Sub(n1))
@ -74,7 +78,7 @@ func squareCapper(p Adder, halfWidth Fix32, pivot, n1 Point) {
// RoundJoiner adds round joins to a stroked path.
var RoundJoiner Joiner = JoinerFunc(roundJoiner)
func roundJoiner(lhs, rhs Adder, haflWidth Fix32, pivot, n0, n1 Point) {
func roundJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
dot := pDot(pRot90CW(n0), n1)
if dot >= 0 {
addArc(lhs, pivot, n0, n1)
@ -88,7 +92,7 @@ func roundJoiner(lhs, rhs Adder, haflWidth Fix32, pivot, n0, n1 Point) {
// BevelJoiner adds bevel joins to a stroked path.
var BevelJoiner Joiner = JoinerFunc(bevelJoiner)
func bevelJoiner(lhs, rhs Adder, haflWidth Fix32, pivot, n0, n1 Point) {
func bevelJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
lhs.Add1(pivot.Add(n1))
rhs.Add1(pivot.Sub(n1))
}
@ -96,7 +100,7 @@ func bevelJoiner(lhs, rhs Adder, haflWidth Fix32, pivot, n0, n1 Point) {
// addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of
// the two possible arcs is taken, i.e. the one spanning <= 180 degrees. The
// two vectors n0 and n1 must be of equal length.
func addArc(p Adder, pivot, n0, n1 Point) {
func addArc(p Adder, pivot, n0, n1 fixed.Point26_6) {
// r2 is the square of the length of n0.
r2 := pDot(n0, n0)
if r2 < epsilon {
@ -109,7 +113,7 @@ func addArc(p Adder, pivot, n0, n1 Point) {
// control points {1, 0}, {1, tan(π/8)} and {1/√2, 1/√2} suitably scaled,
// rotated and translated. tan(π/8) is approximately 106/256.
const tpo8 = 106
var s Point
var s fixed.Point26_6
// We determine which octant the angle between n0 and n1 is in via three
// dot products. m0, m1 and m2 are n0 rotated clockwise by 45, 90 and 135
// degrees.
@ -178,28 +182,28 @@ func addArc(p Adder, pivot, n0, n1 Point) {
// d is the normalized dot product between s and n1. Since the angle ranges
// between 0 and 45 degrees then d ranges between 256/256 and 181/256.
d := 256 * pDot(s, n1) / r2
multiple := Fix32(150 - 22*(d-181)/(256-181))
multiple := fixed.Int26_6(150-(150-128)*(d-181)/(256-181)) >> 2
p.Add2(pivot.Add(s.Add(n1).Mul(multiple)), pivot.Add(n1))
}
// midpoint returns the midpoint of two Points.
func midpoint(a, b Point) Point {
return Point{(a.X + b.X) / 2, (a.Y + b.Y) / 2}
func midpoint(a, b fixed.Point26_6) fixed.Point26_6 {
return fixed.Point26_6{(a.X + b.X) / 2, (a.Y + b.Y) / 2}
}
// angleGreaterThan45 returns whether the angle between two vectors is more
// than 45 degrees.
func angleGreaterThan45(v0, v1 Point) bool {
func angleGreaterThan45(v0, v1 fixed.Point26_6) bool {
v := pRot45CCW(v0)
return pDot(v, v1) < 0 || pDot(pRot90CW(v), v1) < 0
}
// interpolate returns the point (1-t)*a + t*b.
func interpolate(a, b Point, t Fix64) Point {
s := 1<<16 - t
x := s*Fix64(a.X) + t*Fix64(b.X)
y := s*Fix64(a.Y) + t*Fix64(b.Y)
return Point{Fix32(x >> 16), Fix32(y >> 16)}
func interpolate(a, b fixed.Point26_6, t fixed.Int52_12) fixed.Point26_6 {
s := 1<<12 - t
x := s*fixed.Int52_12(a.X) + t*fixed.Int52_12(b.X)
y := s*fixed.Int52_12(a.Y) + t*fixed.Int52_12(b.Y)
return fixed.Point26_6{fixed.Int26_6(x >> 12), fixed.Int26_6(y >> 12)}
}
// curviest2 returns the value of t for which the quadratic parametric curve
@ -216,15 +220,15 @@ func interpolate(a, b Point, t Fix64) Point {
// (x²+y²) is extreme. The first order condition is that
// 2*x*x″+2*y*y″ = 0, or (dx+ex*t)*ex + (dy+ey*t)*ey = 0.
// Solving for t gives t = -(dx*ex+dy*ey) / (ex*ex+ey*ey).
func curviest2(a, b, c Point) Fix64 {
func curviest2(a, b, c fixed.Point26_6) fixed.Int52_12 {
dx := int64(b.X - a.X)
dy := int64(b.Y - a.Y)
ex := int64(c.X - 2*b.X + a.X)
ey := int64(c.Y - 2*b.Y + a.Y)
if ex == 0 && ey == 0 {
return 32768
return 2048
}
return Fix64(-65536 * (dx*ex + dy*ey) / (ex*ex + ey*ey))
return fixed.Int52_12(-4096 * (dx*ex + dy*ey) / (ex*ex + ey*ey))
}
// A stroker holds state for stroking a path.
@ -232,7 +236,7 @@ type stroker struct {
// p is the destination that records the stroked path.
p Adder
// u is the half-width of the stroke.
u Fix32
u fixed.Int26_6
// cr and jr specify how to end and connect path segments.
cr Capper
jr Joiner
@ -242,19 +246,19 @@ type stroker struct {
r Path
// a is the most recent segment point. anorm is the segment normal of
// length u at that point.
a, anorm Point
a, anorm fixed.Point26_6
}
// addNonCurvy2 adds a quadratic segment to the stroker, where the segment
// defined by (k.a, b, c) achieves maximum curvature at either k.a or c.
func (k *stroker) addNonCurvy2(b, c Point) {
func (k *stroker) addNonCurvy2(b, c fixed.Point26_6) {
// We repeatedly divide the segment at its middle until it is straight
// enough to approximate the stroke by just translating the control points.
// ds and ps are stacks of depths and points. t is the top of the stack.
const maxDepth = 5
var (
ds [maxDepth + 1]int
ps [2*maxDepth + 3]Point
ps [2*maxDepth + 3]fixed.Point26_6
t int
)
// Initially the ps stack has one quadratic segment of depth zero.
@ -263,7 +267,7 @@ func (k *stroker) addNonCurvy2(b, c Point) {
ps[1] = b
ps[0] = c
anorm := k.anorm
var cnorm Point
var cnorm fixed.Point26_6
for {
depth := ds[t]
@ -272,8 +276,8 @@ func (k *stroker) addNonCurvy2(b, c Point) {
c := ps[2*t+0]
ab := b.Sub(a)
bc := c.Sub(b)
abIsSmall := pDot(ab, ab) < Fix64(1<<16)
bcIsSmall := pDot(bc, bc) < Fix64(1<<16)
abIsSmall := pDot(ab, ab) < fixed.Int52_12(1<<12)
bcIsSmall := pDot(bc, bc) < fixed.Int52_12(1<<12)
if abIsSmall && bcIsSmall {
// Approximate the segment by a circular arc.
cnorm = pRot90CCW(pNorm(bc, k.u))
@ -310,7 +314,7 @@ func (k *stroker) addNonCurvy2(b, c Point) {
}
// Add1 adds a linear segment to the stroker.
func (k *stroker) Add1(b Point) {
func (k *stroker) Add1(b fixed.Point26_6) {
bnorm := pRot90CCW(pNorm(b.Sub(k.a), k.u))
if len(k.r) == 0 {
k.p.Start(k.a.Add(bnorm))
@ -324,7 +328,7 @@ func (k *stroker) Add1(b Point) {
}
// Add2 adds a quadratic segment to the stroker.
func (k *stroker) Add2(b, c Point) {
func (k *stroker) Add2(b, c fixed.Point26_6) {
ab := b.Sub(k.a)
bc := c.Sub(b)
abnorm := pRot90CCW(pNorm(ab, k.u))
@ -349,7 +353,7 @@ func (k *stroker) Add2(b, c Point) {
// The quadratic segment (k.a, b, c) has a point of maximum curvature.
// If this occurs at an end point, we process the segment as a whole.
t := curviest2(k.a, b, c)
if t <= 0 || 65536 <= t {
if t <= 0 || 4096 <= t {
k.addNonCurvy2(b, c)
return
}
@ -364,7 +368,7 @@ func (k *stroker) Add2(b, c Point) {
// then the decomposition can become unstable, so we approximate the
// quadratic segment by two linear segments joined by an arc.
bcnorm := pRot90CCW(pNorm(bc, k.u))
if pDot(abnorm, bcnorm) < -Fix64(k.u)*Fix64(k.u)*2047/2048 {
if pDot(abnorm, bcnorm) < -fixed.Int52_12(k.u)*fixed.Int52_12(k.u)*2047/2048 {
pArc := pDot(abnorm, bc) < 0
k.p.Add1(mabc.Add(abnorm))
@ -395,7 +399,7 @@ func (k *stroker) Add2(b, c Point) {
}
// Add3 adds a cubic segment to the stroker.
func (k *stroker) Add3(b, c, d Point) {
func (k *stroker) Add3(b, c, d fixed.Point26_6) {
panic("freetype/raster: stroke unimplemented for cubic segments")
}
@ -406,17 +410,26 @@ func (k *stroker) stroke(q Path) {
// path is accumulated in k.r. Once we've finished adding the LHS to k.p,
// we add the RHS in reverse order.
k.r = make(Path, 0, len(q))
k.a = Point{q[1], q[2]}
k.a = fixed.Point26_6{q[1], q[2]}
for i := 4; i < len(q); {
switch q[i] {
case 1:
k.Add1(Point{q[i+1], q[i+2]})
k.Add1(
fixed.Point26_6{q[i+1], q[i+2]},
)
i += 4
case 2:
k.Add2(Point{q[i+1], q[i+2]}, Point{q[i+3], q[i+4]})
k.Add2(
fixed.Point26_6{q[i+1], q[i+2]},
fixed.Point26_6{q[i+3], q[i+4]},
)
i += 6
case 3:
k.Add3(Point{q[i+1], q[i+2]}, Point{q[i+3], q[i+4]}, Point{q[i+5], q[i+6]})
k.Add3(
fixed.Point26_6{q[i+1], q[i+2]},
fixed.Point26_6{q[i+3], q[i+4]},
fixed.Point26_6{q[i+5], q[i+6]},
)
i += 8
default:
panic("freetype/raster: bad path")
@ -430,13 +443,13 @@ func (k *stroker) stroke(q Path) {
k.cr.Cap(k.p, k.u, q.lastPoint(), pNeg(k.anorm))
addPathReversed(k.p, k.r)
pivot := q.firstPoint()
k.cr.Cap(k.p, k.u, pivot, pivot.Sub(Point{k.r[1], k.r[2]}))
k.cr.Cap(k.p, k.u, pivot, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]}))
}
// Stroke adds q stroked with the given width to p. The result is typically
// self-intersecting and should be rasterized with UseNonZeroWinding.
// cr and jr may be nil, which defaults to a RoundCapper or RoundJoiner.
func Stroke(p Adder, q Path, width Fix32, cr Capper, jr Joiner) {
func Stroke(p Adder, q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
if len(q) == 0 {
return
}