From 856a70c3951a3ffe7a6c0dd5bb6729813eda12e3 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Mon, 17 Aug 2015 10:26:09 +1000 Subject: [PATCH] Use the fixed.Int26_6 and fixed.Point26_6 types. --- example/gamma/main.go | 9 +- example/raster/main.go | 11 +-- example/round/main.go | 51 +++++------ freetype.go | 94 +++++++++++---------- raster/geom.go | 149 ++++++++++++-------------------- raster/raster.go | 187 ++++++++++++++++++++++------------------- raster/stroke.go | 103 +++++++++++++---------- 7 files changed, 298 insertions(+), 306 deletions(-) diff --git a/example/gamma/main.go b/example/gamma/main.go index adae4ea..cc852ba 100644 --- a/example/gamma/main.go +++ b/example/gamma/main.go @@ -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), } } diff --git a/example/raster/main.go b/example/raster/main.go index a8d0baa..ebe6c36 100644 --- a/example/raster/main.go +++ b/example/raster/main.go @@ -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 } diff --git a/example/round/main.go b/example/round/main.go index 5115a1d..c48cad3 100644 --- a/example/round/main.go +++ b/example/round/main.go @@ -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}) } } diff --git a/freetype.go b/freetype.go index 0c16c22..291935d 100644 --- a/freetype.go +++ b/freetype.go @@ -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) diff --git a/raster/geom.go b/raster/geom.go index d2188ce..f3696ea 100644 --- a/raster/geom.go +++ b/raster/geom.go @@ -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") } diff --git a/raster/raster.go b/raster/raster.go index 3829c30..a666cb4 100644 --- a/raster/raster.go +++ b/raster/raster.go @@ -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 { diff --git a/raster/stroke.go b/raster/stroke.go index a46a107..8d43797 100644 --- a/raster/stroke.go +++ b/raster/stroke.go @@ -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 }