freetype: cache glyphs during DrawText.
A freetype.Context now has dst and src image setters and the notion of a current point, instead of having to pass a painter and pt to DrawText. R=r, rsc CC=golang-dev http://codereview.appspot.com/2169041
This commit is contained in:
parent
c92eed8b0c
commit
84097fa410
2 changed files with 151 additions and 70 deletions
|
@ -11,7 +11,6 @@ import (
|
|||
"flag"
|
||||
"fmt"
|
||||
"freetype-go.googlecode.com/hg/freetype"
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
"image"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
|
@ -22,7 +21,6 @@ import (
|
|||
var (
|
||||
dpi = flag.Int("dpi", 72, "screen resolution in Dots Per Inch")
|
||||
fontfile = flag.String("fontfile", "../../luxi-fonts/luxisr.ttf", "filename of the ttf font")
|
||||
gamma = flag.Float("gamma", 1.0, "gamma correction")
|
||||
size = flag.Float("size", 12, "font size in points")
|
||||
spacing = flag.Float("spacing", 1.5, "line spacing (e.g. 2 means double spaced)")
|
||||
wonb = flag.Bool("whiteonblack", false, "white text on a black background")
|
||||
|
@ -93,9 +91,8 @@ func main() {
|
|||
c.SetDPI(*dpi)
|
||||
c.SetFont(font)
|
||||
c.SetFontSize(*size)
|
||||
rp := raster.NewRGBAPainter(rgba)
|
||||
rp.SetColor(fg)
|
||||
gcp := raster.NewGammaCorrectionPainter(rp, *gamma)
|
||||
c.SetDst(rgba)
|
||||
c.SetSrc(fg)
|
||||
|
||||
// Draw the guidelines.
|
||||
for i := 0; i < 200; i++ {
|
||||
|
@ -106,7 +103,8 @@ func main() {
|
|||
// Draw the text.
|
||||
pt := freetype.Pt(10, 10+c.FUnitToPixelRU(font.UnitsPerEm()))
|
||||
for _, s := range text {
|
||||
err = c.DrawText(gcp, pt, s)
|
||||
c.SetPoint(pt)
|
||||
err = c.DrawText(s)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
return
|
||||
|
|
|
@ -9,11 +9,33 @@
|
|||
package freetype
|
||||
|
||||
import (
|
||||
"exp/draw"
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
"freetype-go.googlecode.com/hg/freetype/truetype"
|
||||
"image"
|
||||
"os"
|
||||
)
|
||||
|
||||
// These constants determine the size of the glyph cache. The cache is keyed
|
||||
// primarily by the glyph index modulo nGlyphs, and secondarily by sub-pixel
|
||||
// position for the mask image. Sub-pixel positions are quantized to
|
||||
// nXFractions possible values in both the x and y directions.
|
||||
const (
|
||||
nGlyphs = 256
|
||||
nXFractions = 4
|
||||
nYFractions = 1
|
||||
)
|
||||
|
||||
// An entry in the glyph cache is keyed explicitly by the glyph index and
|
||||
// implicitly by the quantized x and y fractional offset. It maps to a mask
|
||||
// image and an offset.
|
||||
type cacheEntry struct {
|
||||
valid bool
|
||||
glyph truetype.Index
|
||||
mask *image.Alpha
|
||||
offset image.Point
|
||||
}
|
||||
|
||||
// ParseFont just calls the Parse function from the freetype/truetype package.
|
||||
// It is provided here so that code that imports this package doesn't need
|
||||
// to also include the freetype/truetype package.
|
||||
|
@ -32,15 +54,12 @@ type Context struct {
|
|||
r *raster.Rasterizer
|
||||
font *truetype.Font
|
||||
glyphBuf *truetype.GlyphBuf
|
||||
fontSize float
|
||||
dpi int
|
||||
upe int
|
||||
// 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.
|
||||
xmin, ymin int
|
||||
// pt is the location where drawing starts.
|
||||
pt raster.Point
|
||||
// dst and src are the destination and source images for drawing.
|
||||
dst draw.Image
|
||||
src image.Image
|
||||
// fontSize, dpi and upe are used to calculate scale.
|
||||
// scale is a multiplication factor to convert 256 FUnits (which is truetype's
|
||||
// native unit) to 24.8 fixed point units (which is the rasterizer's native unit).
|
||||
// At the default values of 72 DPI and 2048 units-per-em, one em of a 12 point
|
||||
|
@ -50,7 +69,12 @@ type Context struct {
|
|||
// which equals 384 fixed point units per 256 FUnits.
|
||||
// To check this, 1 em * 2048 FUnits per em * 384 fixed point units per 256 FUnits
|
||||
// equals 3072 fixed point units.
|
||||
scale int
|
||||
fontSize float
|
||||
dpi int
|
||||
upe int
|
||||
scale int
|
||||
// cache is the glyph cache.
|
||||
cache [nGlyphs * nXFractions * nYFractions]cacheEntry
|
||||
}
|
||||
|
||||
// FUnitToFix32 converts the given number of FUnits into fixed point units,
|
||||
|
@ -124,89 +148,126 @@ func (c *Context) drawContour(ps []truetype.Point, dx, dy raster.Fix32) {
|
|||
}
|
||||
}
|
||||
|
||||
// DrawText draws s at pt using p. The text is placed so that the left edge of
|
||||
// the em square of the first character of s and the baseline intersect at pt.
|
||||
// The majority of the affected pixels will be above and to the right of pt,
|
||||
// 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 pt.
|
||||
// pt is a raster.Point and can therefore represent sub-pixel positions.
|
||||
func (c *Context) DrawText(p raster.Painter, pt raster.Point, s string) (err os.Error) {
|
||||
// rasterize returns the 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) (*image.Alpha, image.Point, os.Error) {
|
||||
if err := c.glyphBuf.Load(c.font, glyph); err != nil {
|
||||
return nil, image.ZP, err
|
||||
}
|
||||
// Calculate the integer-pixel bounds for the glyph.
|
||||
xmin := int(fx+c.FUnitToFix32(+int(c.glyphBuf.B.XMin))) >> 8
|
||||
ymin := int(fy+c.FUnitToFix32(-int(c.glyphBuf.B.YMax))) >> 8
|
||||
xmax := int(fx+c.FUnitToFix32(+int(c.glyphBuf.B.XMax))+0xff) >> 8
|
||||
ymax := int(fy+c.FUnitToFix32(-int(c.glyphBuf.B.YMin))+0xff) >> 8
|
||||
if xmin > xmax || ymin > ymax {
|
||||
return nil, image.ZP, os.NewError("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)
|
||||
// Rasterize the glyph's vectors.
|
||||
c.r.Clear()
|
||||
e0 := 0
|
||||
for _, e1 := range c.glyphBuf.End {
|
||||
c.drawContour(c.glyphBuf.Point[e0:e1], fx, fy)
|
||||
e0 = e1
|
||||
}
|
||||
a := image.NewAlpha(xmax-xmin, ymax-ymin)
|
||||
c.r.Rasterize(&raster.AlphaPainter{a, draw.Src})
|
||||
return a, image.Point{xmin, ymin}, nil
|
||||
}
|
||||
|
||||
// glyph returns the 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) (*image.Alpha, image.Point, os.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
|
||||
// Calculate the index t into the cache array.
|
||||
tg := int(glyph) % nGlyphs
|
||||
tx := int(fx) / (256 / nXFractions)
|
||||
ty := int(fy) / (256 / nYFractions)
|
||||
t := ((tg*nXFractions)+tx)*nYFractions + ty
|
||||
// Check for a cache hit.
|
||||
if c.cache[t].valid && c.cache[t].glyph == glyph {
|
||||
return c.cache[t].mask, c.cache[t].offset.Add(image.Point{ix, iy}), nil
|
||||
}
|
||||
// Rasterize the glyph and put the result into the cache.
|
||||
mask, offset, err := c.rasterize(glyph, fx, fy)
|
||||
if err != nil {
|
||||
return nil, image.ZP, err
|
||||
}
|
||||
c.cache[t] = cacheEntry{true, glyph, mask, offset}
|
||||
return mask, offset.Add(image.Point{ix, iy}), nil
|
||||
}
|
||||
|
||||
// DrawText draws s at the current point and then advances the point. The text
|
||||
// is placed so that the left edge of the em square of the first character of s
|
||||
// and the baseline intersect at the point. The majority of the affected pixels
|
||||
// will be 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.
|
||||
func (c *Context) DrawText(s string) os.Error {
|
||||
if c.font == nil {
|
||||
return os.NewError("freetype: DrawText called with a nil font")
|
||||
}
|
||||
// pt.X, pt.Y, x, y, dx, dy and x0 are measured in raster.Fix32 units,
|
||||
// c.r.Dx, c.r.Dy, c.xmin and c.ymin are measured in pixels, and
|
||||
// advance is measured in FUnits.
|
||||
var x, y raster.Fix32
|
||||
advance, x0 := 0, pt.X
|
||||
dx := raster.Fix32(-c.xmin << 8)
|
||||
dy := raster.Fix32(-c.ymin << 8)
|
||||
c.r.Dy, y = c.ymin+int(pt.Y>>8), pt.Y&0xff
|
||||
y += dy
|
||||
prev, hasPrev := truetype.Index(0), false
|
||||
for _, ch := range s {
|
||||
index := c.font.Index(ch)
|
||||
// Load the next glyph (if it was different from the previous one)
|
||||
// and add any kerning adjustment.
|
||||
for _, rune := range s {
|
||||
index := c.font.Index(rune)
|
||||
if hasPrev {
|
||||
advance += int(c.font.Kerning(prev, index))
|
||||
if prev != index {
|
||||
err = c.glyphBuf.Load(c.font, index)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = c.glyphBuf.Load(c.font, index)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.pt.X += c.FUnitToFix32(int(c.font.Kerning(prev, index)))
|
||||
}
|
||||
// Convert the advance from FUnits to raster.Fix32 units.
|
||||
x = x0 + c.FUnitToFix32(advance)
|
||||
// Break the co-ordinate down into an integer pixel part and a
|
||||
// sub-pixel part, making sure that the latter is non-negative.
|
||||
c.r.Dx, x = c.xmin+int(x>>8), x&0xff
|
||||
x += dx
|
||||
// Draw the contours.
|
||||
c.r.Clear()
|
||||
e0 := 0
|
||||
for _, e := range c.glyphBuf.End {
|
||||
c.drawContour(c.glyphBuf.Point[e0:e], x, y)
|
||||
e0 = e
|
||||
mask, offset, err := c.glyph(index, c.pt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.r.Rasterize(p)
|
||||
// Advance the cursor.
|
||||
advance += int(c.font.HMetric(index).AdvanceWidth)
|
||||
c.pt.X += c.FUnitToFix32(int(c.font.HMetric(index).AdvanceWidth))
|
||||
draw.DrawMask(c.dst, mask.Bounds().Add(offset), c.src, image.ZP, mask, image.ZP, draw.Over)
|
||||
prev, hasPrev = index, true
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// recalc recalculates scale and bounds values from the font size, screen
|
||||
// resolution and font metrics.
|
||||
// resolution and font metrics, and invalidates the glyph cache.
|
||||
func (c *Context) recalc() {
|
||||
c.scale = int((c.fontSize * float(c.dpi) * 256 * 256) / (float(c.upe) * 72))
|
||||
if c.font == nil {
|
||||
c.xmin, c.ymin = 0, 0
|
||||
c.r.SetBounds(0, 0)
|
||||
} else {
|
||||
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
|
||||
b := c.font.Bounds()
|
||||
c.xmin = c.FUnitToPixelRD(int(b.XMin))
|
||||
c.ymin = c.FUnitToPixelRD(-int(b.YMax))
|
||||
xmax := c.FUnitToPixelRU(int(b.XMax))
|
||||
xmin := c.FUnitToPixelRD(+int(b.XMin))
|
||||
ymin := c.FUnitToPixelRD(-int(b.YMax))
|
||||
xmax := c.FUnitToPixelRU(+int(b.XMax))
|
||||
ymax := c.FUnitToPixelRU(-int(b.YMin))
|
||||
c.r.SetBounds(xmax-c.xmin, ymax-c.ymin)
|
||||
c.r.SetBounds(xmax-xmin, ymax-ymin)
|
||||
}
|
||||
for i := range c.cache {
|
||||
c.cache[i] = cacheEntry{}
|
||||
}
|
||||
}
|
||||
|
||||
// SetDPI sets the screen resolution in dots per inch.
|
||||
func (c *Context) SetDPI(dpi int) {
|
||||
if c.dpi == dpi {
|
||||
return
|
||||
}
|
||||
c.dpi = dpi
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetFont sets the font used to draw text.
|
||||
func (c *Context) SetFont(font *truetype.Font) {
|
||||
if c.font == font {
|
||||
return
|
||||
}
|
||||
c.font = font
|
||||
c.upe = font.UnitsPerEm()
|
||||
if c.upe <= 0 {
|
||||
|
@ -217,10 +278,32 @@ func (c *Context) SetFont(font *truetype.Font) {
|
|||
|
||||
// SetFontSize sets the font size in points (as in ``a 12 point font'').
|
||||
func (c *Context) SetFontSize(fontSize float) {
|
||||
if c.fontSize == fontSize {
|
||||
return
|
||||
}
|
||||
c.fontSize = fontSize
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetDst sets the destination image for draw operations.
|
||||
func (c *Context) SetDst(dst draw.Image) {
|
||||
c.dst = dst
|
||||
}
|
||||
|
||||
// SetSrc sets the source image for draw operations. This is typically an
|
||||
// image.ColorImage.
|
||||
func (c *Context) SetSrc(src image.Image) {
|
||||
c.src = src
|
||||
}
|
||||
|
||||
// SetPoint sets the point where DrawText will next draw text.
|
||||
// pt is a raster.Point and can therefore represent sub-pixel positions.
|
||||
func (c *Context) SetPoint(pt raster.Point) {
|
||||
c.pt = pt
|
||||
}
|
||||
|
||||
// TODO(nigeltao): implement Context.SetGamma.
|
||||
|
||||
// NewContext creates a new Context.
|
||||
func NewContext() *Context {
|
||||
return &Context{
|
||||
|
|
Loading…
Reference in a new issue