Freetype-Go: new freetype package to provide a convenience API to
draw text onto an image. This is a simple API that doesn't handle line breaking, ligatures, right-to-left or vertical scripts, and other fancy features. R=r, rsc CC=golang-dev http://codereview.appspot.com/1121045
This commit is contained in:
parent
7b02573579
commit
25c38cfec1
9 changed files with 568 additions and 75 deletions
127
example/freetype/main.go
Normal file
127
example/freetype/main.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"exp/draw"
|
||||
"flag"
|
||||
"fmt"
|
||||
"freetype-go.googlecode.com/hg/freetype"
|
||||
"image"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
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")
|
||||
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")
|
||||
)
|
||||
|
||||
var text = []string{
|
||||
"’Twas brillig, and the slithy toves",
|
||||
"Did gyre and gimble in the wabe;",
|
||||
"All mimsy were the borogoves,",
|
||||
"And the mome raths outgrabe.",
|
||||
"",
|
||||
"“Beware the Jabberwock, my son!",
|
||||
"The jaws that bite, the claws that catch!",
|
||||
"Beware the Jubjub bird, and shun",
|
||||
"The frumious Bandersnatch!”",
|
||||
"",
|
||||
"He took his vorpal sword in hand:",
|
||||
"Long time the manxome foe he sought—",
|
||||
"So rested he by the Tumtum tree,",
|
||||
"And stood awhile in thought.",
|
||||
"",
|
||||
"And as in uffish thought he stood,",
|
||||
"The Jabberwock, with eyes of flame,",
|
||||
"Came whiffling through the tulgey wood,",
|
||||
"And burbled as it came!",
|
||||
"",
|
||||
"One, two! One, two! and through and through",
|
||||
"The vorpal blade went snicker-snack!",
|
||||
"He left it dead, and with its head",
|
||||
"He went galumphing back.",
|
||||
"",
|
||||
"“And hast thou slain the Jabberwock?",
|
||||
"Come to my arms, my beamish boy!",
|
||||
"O frabjous day! Callooh! Callay!”",
|
||||
"He chortled in his joy.",
|
||||
"",
|
||||
"’Twas brillig, and the slithy toves",
|
||||
"Did gyre and gimble in the wabe;",
|
||||
"All mimsy were the borogoves,",
|
||||
"And the mome raths outgrabe.",
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Read the font data.
|
||||
fontBytes, err := ioutil.ReadFile(*fontfile)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
return
|
||||
}
|
||||
font, err := freetype.ParseFont(fontBytes)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the context.
|
||||
fg, bg := image.Black, image.White
|
||||
ruler := image.RGBAColor{0xdd, 0xdd, 0xdd, 0xff}
|
||||
if *wonb {
|
||||
fg, bg = image.White, image.Black
|
||||
ruler = image.RGBAColor{0x22, 0x22, 0x22, 0xff}
|
||||
}
|
||||
rgba := image.NewRGBA(640, 480)
|
||||
draw.Draw(rgba, draw.Rect(0, 0, rgba.Width(), rgba.Height()), bg, draw.ZP)
|
||||
c := freetype.NewRGBAContext(rgba)
|
||||
c.SetColor(fg)
|
||||
c.SetDPI(*dpi)
|
||||
c.SetFont(font)
|
||||
c.SetFontSize(*size)
|
||||
|
||||
// Draw the guidelines.
|
||||
for i := 0; i < 200; i++ {
|
||||
rgba.Set(10, 10+i, ruler)
|
||||
rgba.Set(10+i, 10, ruler)
|
||||
}
|
||||
|
||||
// Draw the text.
|
||||
pt := freetype.Pt(10, 10)
|
||||
for _, s := range text {
|
||||
err = c.DrawText(pt, s)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
return
|
||||
}
|
||||
pt.Y += c.PointToFixed(*size * *spacing)
|
||||
}
|
||||
|
||||
// Save that RGBA image to disk.
|
||||
f, err := os.Open("out.png", os.O_CREAT|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
b := bufio.NewWriter(f)
|
||||
err = png.Encode(b, rgba)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = b.Flush()
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Wrote out.png OK.")
|
||||
}
|
|
@ -31,7 +31,7 @@ func clear(m *image.Alpha) {
|
|||
|
||||
func main() {
|
||||
// Draw a rounded corner that is one pixel wide.
|
||||
r := raster.New(50, 50)
|
||||
r := raster.NewRasterizer(50, 50)
|
||||
r.Start(p(5, 5))
|
||||
r.Add1(p(5, 25))
|
||||
r.Add2(p(5, 45), p(25, 45))
|
||||
|
@ -51,11 +51,12 @@ func main() {
|
|||
draw.Draw(rgba, draw.Rect(0, 0, w, h/2), image.Black, draw.ZP)
|
||||
draw.Draw(rgba, draw.Rect(0, h/2, w, h), image.White, draw.ZP)
|
||||
mask := image.NewAlpha(50, 50)
|
||||
painter := raster.AlphaSrcPainter(mask)
|
||||
painter := raster.NewAlphaPainter(mask)
|
||||
painter.Op = draw.Src
|
||||
gammas := []float64{1.0 / 10.0, 1.0 / 3.0, 1.0 / 2.0, 2.0 / 3.0, 4.0 / 5.0, 1.0, 5.0 / 4.0, 3.0 / 2.0, 2.0, 3.0, 10.0}
|
||||
for i, g := range gammas {
|
||||
clear(mask)
|
||||
r.Rasterize(raster.GammaCorrectionPainter(painter, g))
|
||||
r.Rasterize(raster.NewGammaCorrectionPainter(painter, g))
|
||||
x, y := 50*i+25, 25
|
||||
draw.DrawMask(rgba, draw.Rect(x, y, x+50, y+50), image.White, draw.ZP, mask, draw.ZP, draw.Over)
|
||||
y += 100
|
||||
|
|
|
@ -137,11 +137,13 @@ func main() {
|
|||
w = 400
|
||||
h = 400
|
||||
)
|
||||
r := raster.New(w, h)
|
||||
r := raster.NewRasterizer(w, h)
|
||||
contour(r, outside)
|
||||
contour(r, inside)
|
||||
mask := image.NewAlpha(w, h)
|
||||
r.Rasterize(raster.AlphaSrcPainter(mask))
|
||||
p := raster.NewAlphaPainter(mask)
|
||||
p.Op = draw.Src
|
||||
r.Rasterize(p)
|
||||
|
||||
// Draw the mask image (in gray) onto an RGBA image.
|
||||
rgba := image.NewRGBA(w, h)
|
||||
|
|
|
@ -20,7 +20,7 @@ func printBounds(b truetype.Bounds) {
|
|||
fmt.Printf("XMin:%d YMin:%d XMax:%d YMax:%d\n", b.XMin, b.YMin, b.XMax, b.YMax)
|
||||
}
|
||||
|
||||
func printGlyph(g *truetype.Glyph) {
|
||||
func printGlyph(g *truetype.GlyphBuf) {
|
||||
printBounds(g.B)
|
||||
fmt.Print("Points:\n---\n")
|
||||
e := 0
|
||||
|
@ -58,7 +58,7 @@ func main() {
|
|||
|
||||
i0 := font.Index(c0)
|
||||
hm := font.HMetric(i0)
|
||||
g := truetype.NewGlyph()
|
||||
g := truetype.NewGlyphBuf()
|
||||
err = g.Load(font, i0)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
|
|
13
freetype/Makefile
Normal file
13
freetype/Makefile
Normal file
|
@ -0,0 +1,13 @@
|
|||
# Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by your choice of either the
|
||||
# FreeType License or the GNU General Public License version 2,
|
||||
# both of which can be found in the LICENSE file.
|
||||
|
||||
include $(GOROOT)/src/Make.$(GOARCH)
|
||||
|
||||
TARG=freetype-go.googlecode.com/hg/freetype
|
||||
GOFILES=\
|
||||
freetype.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
|
248
freetype/freetype.go
Normal file
248
freetype/freetype.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by your choice of either the
|
||||
// FreeType License or the GNU General Public License version 2,
|
||||
// both of which can be found in the LICENSE file.
|
||||
|
||||
// The freetype package provides a convenient API to draw text onto an image.
|
||||
// Use the freetype/raster and freetype/truetype packages for lower level
|
||||
// control over rasterization and TrueType parsing.
|
||||
package freetype
|
||||
|
||||
import (
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
"freetype-go.googlecode.com/hg/freetype/truetype"
|
||||
"image"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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.
|
||||
func ParseFont(b []byte) (*truetype.Font, os.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.Fixed units.
|
||||
func Pt(x, y int) raster.Point {
|
||||
return raster.Point{raster.Fixed(x << 8), raster.Fixed(y << 8)}
|
||||
}
|
||||
|
||||
// An RGBAContext holds the state for drawing text from a given font at a
|
||||
// given size.
|
||||
type RGBAContext struct {
|
||||
r *raster.Rasterizer
|
||||
p *raster.RGBAPainter
|
||||
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
|
||||
// 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
|
||||
// font is 12 pixels, which is 3072 fixed point units, and scale is
|
||||
// (pointSize * resolution * 256 * 256) / (unitsPerEm * 72), or
|
||||
// (12 * 72 * 256 * 256) / (2048 * 72),
|
||||
// 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
|
||||
}
|
||||
|
||||
// FUnitToFixed converts the given number of FUnits into fixed point units,
|
||||
// rounding to nearest.
|
||||
func (c *RGBAContext) FUnitToFixed(x int) raster.Fixed {
|
||||
return raster.Fixed((x*c.scale + 128) >> 8)
|
||||
}
|
||||
|
||||
// FUnitToPixelRD converts the given number of FUnits into pixel units,
|
||||
// rounding down.
|
||||
func (c *RGBAContext) FUnitToPixelRD(x int) int {
|
||||
return x * c.scale >> 16
|
||||
}
|
||||
|
||||
// FUnitToPixelRU converts the given number of FUnits into pixel units,
|
||||
// rounding up.
|
||||
func (c *RGBAContext) FUnitToPixelRU(x int) int {
|
||||
return (x*c.scale + 0xffff) >> 16
|
||||
}
|
||||
|
||||
// PointToFixed converts the given number of points (as in ``a 12 point font'')
|
||||
// into fixed point units.
|
||||
func (c *RGBAContext) PointToFixed(x float) raster.Fixed {
|
||||
return raster.Fixed(x * float(c.dpi) * (256.0 / 72.0))
|
||||
}
|
||||
|
||||
// drawContour draws the given closed contour with the given offset.
|
||||
func (c *RGBAContext) drawContour(ps []truetype.Point, dx, dy raster.Fixed) {
|
||||
if len(ps) == 0 {
|
||||
return
|
||||
}
|
||||
// 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{
|
||||
dx + c.FUnitToFixed(int(ps[0].X)),
|
||||
dy + c.FUnitToFixed(c.upe-int(ps[0].Y)),
|
||||
}
|
||||
c.r.Start(start)
|
||||
q0, on0 := start, true
|
||||
for _, p := range ps[1:] {
|
||||
q := raster.Point{
|
||||
dx + c.FUnitToFixed(int(p.X)),
|
||||
dy + c.FUnitToFixed(c.upe-int(p.Y)),
|
||||
}
|
||||
on := p.Flags&0x01 != 0
|
||||
if on {
|
||||
if on0 {
|
||||
c.r.Add1(q)
|
||||
} else {
|
||||
c.r.Add2(q0, q)
|
||||
}
|
||||
} else {
|
||||
if on0 {
|
||||
// No-op.
|
||||
} else {
|
||||
mid := raster.Point{
|
||||
(q0.X + q.X) / 2,
|
||||
(q0.Y + q.Y) / 2,
|
||||
}
|
||||
c.r.Add2(q0, mid)
|
||||
}
|
||||
}
|
||||
q0, on0 = q, on
|
||||
}
|
||||
// Close the curve.
|
||||
if on0 {
|
||||
c.r.Add1(start)
|
||||
} else {
|
||||
c.r.Add2(q0, start)
|
||||
}
|
||||
}
|
||||
|
||||
// DrawText draws s at pt. The text is placed so that the top left of the em
|
||||
// square of the first character of s is equal to pt. The majority of the
|
||||
// affected pixels will be below and to the right of pt, but some may be above
|
||||
// or to the left. For example, drawing a string that starts with a 'J' in an
|
||||
// italic font may affect pixels to the left of pt.
|
||||
// pt is a raster.Point and can therefore represent sub-pixel positions.
|
||||
func (c *RGBAContext) DrawText(pt raster.Point, s string) (err 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.Fixed units,
|
||||
// c.p.Dx, c.p.Dy, c.xmin and c.ymin are measured in pixels, and
|
||||
// advance is measured in FUnits.
|
||||
var x, y raster.Fixed
|
||||
advance, x0 := 0, pt.X
|
||||
dx := raster.Fixed(-c.xmin << 8)
|
||||
dy := raster.Fixed(-c.ymin << 8)
|
||||
c.p.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.
|
||||
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
|
||||
}
|
||||
}
|
||||
// Convert the advance from FUnits to raster.Fixed units.
|
||||
x = x0 + c.FUnitToFixed(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.p.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
|
||||
}
|
||||
c.r.Rasterize(c.p)
|
||||
// Advance the cursor.
|
||||
advance += int(c.font.HMetric(index).AdvanceWidth)
|
||||
prev, hasPrev = index, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// recalc recalculates scale and bounds values from the font size, screen
|
||||
// resolution and font metrics.
|
||||
func (c *RGBAContext) 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
|
||||
} else {
|
||||
b := c.font.Bounds()
|
||||
c.xmin = c.FUnitToPixelRD(int(b.XMin))
|
||||
c.ymin = c.FUnitToPixelRD(c.upe - int(b.YMax))
|
||||
xmax := c.FUnitToPixelRU(int(b.XMax))
|
||||
ymax := c.FUnitToPixelRU(c.upe - int(b.YMin))
|
||||
c.r.SetBounds(xmax-c.xmin, ymax-c.ymin)
|
||||
}
|
||||
}
|
||||
|
||||
// SetColor sets the color to draw text.
|
||||
func (c *RGBAContext) SetColor(color image.Color) {
|
||||
c.p.SetColor(color)
|
||||
}
|
||||
|
||||
// SetDPI sets the screen resolution in dots per inch.
|
||||
func (c *RGBAContext) SetDPI(dpi int) {
|
||||
c.dpi = dpi
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetFont sets the font used to draw text.
|
||||
func (c *RGBAContext) SetFont(font *truetype.Font) {
|
||||
c.font = font
|
||||
c.upe = font.UnitsPerEm()
|
||||
if c.upe <= 0 {
|
||||
c.upe = 1
|
||||
}
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetFontSize sets the font size in points (as in ``a 12 point font'').
|
||||
func (c *RGBAContext) SetFontSize(fontSize float) {
|
||||
c.fontSize = fontSize
|
||||
c.recalc()
|
||||
}
|
||||
|
||||
// SetRGBA sets the image that the RGBAContext draws onto.
|
||||
func (c *RGBAContext) SetRGBA(m *image.RGBA) {
|
||||
c.p.Image = m
|
||||
}
|
||||
|
||||
// NewRGBAContext creates a new RGBAContext.
|
||||
func NewRGBAContext(m *image.RGBA) *RGBAContext {
|
||||
return &RGBAContext{
|
||||
r: raster.NewRasterizer(0, 0),
|
||||
p: raster.NewRGBAPainter(m),
|
||||
glyphBuf: truetype.NewGlyphBuf(),
|
||||
fontSize: 12,
|
||||
dpi: 72,
|
||||
upe: 2048,
|
||||
scale: (12 * 72 * 256 * 256) / (2048 * 72),
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
package raster
|
||||
|
||||
import (
|
||||
"exp/draw"
|
||||
"image"
|
||||
"math"
|
||||
)
|
||||
|
@ -33,46 +34,138 @@ type PainterFunc func(ss []Span)
|
|||
// Paint just delegates the call to f.
|
||||
func (f PainterFunc) Paint(ss []Span) { f(ss) }
|
||||
|
||||
// AlphaOverPainter returns a Painter that paints onto the given Alpha image
|
||||
// using the "src over dst" Porter-Duff composition operator.
|
||||
func AlphaOverPainter(m *image.Alpha) Painter {
|
||||
return PainterFunc(func(ss []Span) {
|
||||
for _, s := range ss {
|
||||
// An AlphaPainter is a Painter that paints Spans onto an image.Alpha.
|
||||
type AlphaPainter struct {
|
||||
// The image to compose onto.
|
||||
Image *image.Alpha
|
||||
// The Porter-Duff composition operator.
|
||||
Op draw.Op
|
||||
// An offset (in pixels) to the painted spans.
|
||||
Dx, Dy int
|
||||
}
|
||||
|
||||
// Paint satisfies the Painter interface by painting ss onto an image.Alpha.
|
||||
func (r *AlphaPainter) Paint(ss []Span) {
|
||||
for _, s := range ss {
|
||||
y := r.Dy + s.Y
|
||||
if y < 0 {
|
||||
continue
|
||||
}
|
||||
if y >= len(r.Image.Pixel) {
|
||||
return
|
||||
}
|
||||
p := r.Image.Pixel[y]
|
||||
x0, x1 := r.Dx+s.X0, r.Dx+s.X1
|
||||
if x0 < 0 {
|
||||
x0 = 0
|
||||
}
|
||||
if x1 > len(p) {
|
||||
x1 = len(p)
|
||||
}
|
||||
if r.Op == draw.Over {
|
||||
a := int(s.A >> 24)
|
||||
p := m.Pixel[s.Y]
|
||||
for i := s.X0; i < s.X1; i++ {
|
||||
ai := int(p[i].A)
|
||||
ai = (ai*255 + (255-ai)*a) / 255
|
||||
p[i] = image.AlphaColor{uint8(ai)}
|
||||
for x := x0; x < x1; x++ {
|
||||
ax := int(p[x].A)
|
||||
ax = (ax*255 + (255-ax)*a) / 255
|
||||
p[x] = image.AlphaColor{uint8(ax)}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// AlphaSrcPainter returns a Painter that paints onto the given Alpha image
|
||||
// using the "src" Porter-Duff composition operator.
|
||||
func AlphaSrcPainter(m *image.Alpha) Painter {
|
||||
return PainterFunc(func(ss []Span) {
|
||||
for _, s := range ss {
|
||||
} else {
|
||||
color := image.AlphaColor{uint8(s.A >> 24)}
|
||||
p := m.Pixel[s.Y]
|
||||
for i := s.X0; i < s.X1; i++ {
|
||||
p[i] = color
|
||||
for x := x0; x < x1; x++ {
|
||||
p[x] = color
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A monochromePainter has a wrapped painter and an accumulator for merging
|
||||
// adjacent opaque Spans.
|
||||
type monochromePainter struct {
|
||||
p Painter
|
||||
// NewAlphaPainter creates a new AlphaPainter for the given image.
|
||||
func NewAlphaPainter(m *image.Alpha) *AlphaPainter {
|
||||
return &AlphaPainter{Image: m}
|
||||
}
|
||||
|
||||
type RGBAPainter struct {
|
||||
// The image to compose onto.
|
||||
Image *image.RGBA
|
||||
// The Porter-Duff composition operator.
|
||||
Op draw.Op
|
||||
// An offset (in pixels) to the painted spans.
|
||||
Dx, Dy int
|
||||
// The 16-bit color to paint the spans.
|
||||
cr, cg, cb, ca uint32
|
||||
}
|
||||
|
||||
// Paint satisfies the Painter interface by painting ss onto an image.RGBA.
|
||||
func (r *RGBAPainter) Paint(ss []Span) {
|
||||
for _, s := range ss {
|
||||
y := r.Dy + s.Y
|
||||
if y < 0 {
|
||||
continue
|
||||
}
|
||||
if y >= len(r.Image.Pixel) {
|
||||
return
|
||||
}
|
||||
p := r.Image.Pixel[y]
|
||||
x0, x1 := r.Dx+s.X0, r.Dx+s.X1
|
||||
if x0 < 0 {
|
||||
x0 = 0
|
||||
}
|
||||
if x1 > len(p) {
|
||||
x1 = len(p)
|
||||
}
|
||||
for x := x0; x < x1; x++ {
|
||||
// This code is duplicated from drawGlyphOver in $GOROOT/src/pkg/exp/draw/draw.go.
|
||||
// TODO(nigeltao): Factor out common code into a utility function, once the compiler
|
||||
// can inline such function calls.
|
||||
ma := s.A >> 16
|
||||
const M = 1<<16 - 1
|
||||
if r.Op == draw.Over {
|
||||
rgba := p[x]
|
||||
dr := uint32(rgba.R)
|
||||
dg := uint32(rgba.G)
|
||||
db := uint32(rgba.B)
|
||||
da := uint32(rgba.A)
|
||||
a := M - (r.ca * ma / M)
|
||||
a *= 0x101
|
||||
dr = (dr*a + r.cr*ma) / M
|
||||
dg = (dg*a + r.cg*ma) / M
|
||||
db = (db*a + r.cb*ma) / M
|
||||
da = (da*a + r.ca*ma) / M
|
||||
p[x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
|
||||
} else {
|
||||
dr := r.cr * ma / M
|
||||
dg := r.cg * ma / M
|
||||
db := r.cb * ma / M
|
||||
da := r.ca * ma / M
|
||||
p[x] = image.RGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetColor sets the color to paint the spans.
|
||||
func (r *RGBAPainter) SetColor(c image.Color) {
|
||||
r.cr, r.cg, r.cb, r.ca = c.RGBA()
|
||||
r.cr >>= 16
|
||||
r.cg >>= 16
|
||||
r.cb >>= 16
|
||||
r.ca >>= 16
|
||||
}
|
||||
|
||||
// NewRGBAPainter creates a new RGBAPainter for the given image.
|
||||
func NewRGBAPainter(m *image.RGBA) *RGBAPainter {
|
||||
return &RGBAPainter{Image: m}
|
||||
}
|
||||
|
||||
// A MonochromePainter wraps another Painter, quantizing each Span's alpha to
|
||||
// be either fully opaque or fully transparent.
|
||||
type MonochromePainter struct {
|
||||
Painter Painter
|
||||
y, x0, x1 int
|
||||
}
|
||||
|
||||
// Paint delegates to the wrapped Painter after quantizing each Span's alpha
|
||||
// values and merging adjacent fully opaque Spans.
|
||||
func (m *monochromePainter) Paint(ss []Span) {
|
||||
// value and merging adjacent fully opaque Spans.
|
||||
func (m *MonochromePainter) Paint(ss []Span) {
|
||||
// We compact the ss slice, discarding any Spans whose alpha quantizes to zero.
|
||||
j := 0
|
||||
for _, s := range ss {
|
||||
|
@ -93,11 +186,11 @@ func (m *monochromePainter) Paint(ss []Span) {
|
|||
if j < len(ss) {
|
||||
ss[j] = Span{}
|
||||
j++
|
||||
m.p.Paint(ss[0:j])
|
||||
m.Painter.Paint(ss[0:j])
|
||||
} else if j == len(ss) {
|
||||
m.p.Paint(ss)
|
||||
m.Painter.Paint(ss)
|
||||
ss[0] = Span{}
|
||||
m.p.Paint(ss[0:1])
|
||||
m.Painter.Paint(ss[0:1])
|
||||
} else {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
@ -106,25 +199,27 @@ func (m *monochromePainter) Paint(ss []Span) {
|
|||
return
|
||||
}
|
||||
}
|
||||
m.p.Paint(ss[0:j])
|
||||
m.Painter.Paint(ss[0:j])
|
||||
}
|
||||
|
||||
// A MonochromePainter wraps another Painter, quantizing each Span's alpha to
|
||||
// be either fully opaque or fully transparent.
|
||||
func MonochromePainter(p Painter) Painter {
|
||||
return &monochromePainter{p: p}
|
||||
// NewMonochromePainter creates a new MonochromePainter that wraps the given
|
||||
// Painter.
|
||||
func NewMonochromePainter(p Painter) *MonochromePainter {
|
||||
return &MonochromePainter{Painter: p}
|
||||
}
|
||||
|
||||
// A gammaCorrectionPainter has a wrapped painter and a precomputed linear
|
||||
// interpolation of the exponential gamma-correction curve.
|
||||
type gammaCorrectionPainter struct {
|
||||
p Painter
|
||||
a [256]uint16 // Alpha values, with fully opaque == 1<<16-1.
|
||||
// A GammaCorrectionPainter wraps another Painter, performing gamma-correction
|
||||
// on each Span's alpha value.
|
||||
type GammaCorrectionPainter struct {
|
||||
// The wrapped Painter.
|
||||
Painter Painter
|
||||
// Precomputed alpha values for linear interpolation, with fully opaque == 1<<16-1.
|
||||
a [256]uint16
|
||||
}
|
||||
|
||||
// Paint delegates to the wrapped Painter after performing gamma-correction
|
||||
// on each Span.
|
||||
func (g *gammaCorrectionPainter) Paint(ss []Span) {
|
||||
func (g *GammaCorrectionPainter) Paint(ss []Span) {
|
||||
const (
|
||||
M = 0x1010101 // 255*M == 1<<32-1
|
||||
N = 0x8080 // N = M>>9, and N < 1<<16-1
|
||||
|
@ -145,17 +240,22 @@ func (g *gammaCorrectionPainter) Paint(ss []Span) {
|
|||
}
|
||||
ss[i].A = a
|
||||
}
|
||||
g.p.Paint(ss)
|
||||
g.Painter.Paint(ss)
|
||||
}
|
||||
|
||||
// A GammaCorrectionPainter wraps another Painter, performing gamma-correction
|
||||
// on the alpha values of each Span.
|
||||
func GammaCorrectionPainter(p Painter, gamma float64) Painter {
|
||||
g := &gammaCorrectionPainter{p: p}
|
||||
// SetGamma sets the gamma value.
|
||||
func (g *GammaCorrectionPainter) SetGamma(gamma float64) {
|
||||
for i := 0; i < 256; i++ {
|
||||
a := float64(i) / 0xff
|
||||
a = math.Pow(a, gamma)
|
||||
g.a[i] = uint16(0xffff * a)
|
||||
}
|
||||
}
|
||||
|
||||
// NewGammaCorrectionPainter creates a new GammaCorrectionPainter that wraps
|
||||
// the given Painter.
|
||||
func NewGammaCorrectionPainter(p Painter, gamma float64) *GammaCorrectionPainter {
|
||||
g := &GammaCorrectionPainter{Painter: p}
|
||||
g.SetGamma(gamma)
|
||||
return g
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ type Point struct {
|
|||
X, Y Fixed
|
||||
}
|
||||
|
||||
// A cell is part of a linked list (for a given yi coordinate) of accumulated
|
||||
// A cell is part of a linked list (for a given yi co-ordinate) of accumulated
|
||||
// area/coverage for the pixel at (xi, yi).
|
||||
type cell struct {
|
||||
xi int
|
||||
|
@ -546,15 +546,15 @@ func (r *Rasterizer) Clear() {
|
|||
}
|
||||
}
|
||||
|
||||
// Creates a new Rasterizer with the given bounds.
|
||||
func New(width, height int) *Rasterizer {
|
||||
// SetBounds sets the maximum width and height of the rasterized image and
|
||||
// calls Clear. The width and height are in pixels, not Fixed units.
|
||||
func (r *Rasterizer) SetBounds(width, height int) {
|
||||
if width < 0 {
|
||||
width = 0
|
||||
}
|
||||
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.
|
||||
|
@ -565,8 +565,6 @@ func New(width, height int) *Rasterizer {
|
|||
ss2, ss3 = 2*ss2, 2*ss3
|
||||
}
|
||||
}
|
||||
|
||||
r := new(Rasterizer)
|
||||
r.width = width
|
||||
r.splitScale2 = ss2
|
||||
r.splitScale3 = ss3
|
||||
|
@ -576,8 +574,12 @@ func New(width, height int) *Rasterizer {
|
|||
} else {
|
||||
r.cellIndex = r.cellIndexBuf[0:height]
|
||||
}
|
||||
for i := 0; i < len(r.cellIndex); i++ {
|
||||
r.cellIndex[i] = -1
|
||||
}
|
||||
r.Clear()
|
||||
}
|
||||
|
||||
// NewRasterizer creates a new Rasterizer with the given bounds.
|
||||
func NewRasterizer(width, height int) *Rasterizer {
|
||||
r := new(Rasterizer)
|
||||
r.SetBounds(width, height)
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -418,9 +418,9 @@ type Point struct {
|
|||
Flags uint8
|
||||
}
|
||||
|
||||
// A Glyph holds a glyph's contours. A Glyph can be re-used to load a series
|
||||
// of glyphs from a Font.
|
||||
type Glyph struct {
|
||||
// A GlyphBuf holds a glyph's contours. A GlyphBuf can be re-used to load a
|
||||
// series of glyphs from a Font.
|
||||
type GlyphBuf struct {
|
||||
// The glyph's bounding box.
|
||||
B Bounds
|
||||
// Point contains all Points from all contours of the glyph.
|
||||
|
@ -433,7 +433,7 @@ type Glyph struct {
|
|||
|
||||
// decodeFlags decodes a glyph's run-length encoded flags,
|
||||
// and returns the remaining data.
|
||||
func (g *Glyph) decodeFlags(d data) data {
|
||||
func (g *GlyphBuf) decodeFlags(d data) data {
|
||||
for i := 0; i < len(g.Point); {
|
||||
c := d.u8()
|
||||
g.Point[i].Flags = c
|
||||
|
@ -450,7 +450,7 @@ func (g *Glyph) decodeFlags(d data) data {
|
|||
}
|
||||
|
||||
// decodeCoords decodes a glyph's delta encoded co-ordinates.
|
||||
func (g *Glyph) decodeCoords(d data) {
|
||||
func (g *GlyphBuf) decodeCoords(d data) {
|
||||
var x int16
|
||||
for i := 0; i < len(g.Point); i++ {
|
||||
f := g.Point[i].Flags
|
||||
|
@ -484,9 +484,9 @@ func (g *Glyph) decodeCoords(d data) {
|
|||
}
|
||||
|
||||
// Load loads a glyph's contours from a Font, overwriting any previously
|
||||
// loaded contours for this Glyph.
|
||||
func (g *Glyph) Load(f *Font, i Index) os.Error {
|
||||
// Reset the Glyph.
|
||||
// loaded contours for this GlyphBuf.
|
||||
func (g *GlyphBuf) Load(f *Font, i Index) os.Error {
|
||||
// Reset the GlyphBuf.
|
||||
g.B = Bounds{}
|
||||
g.Point = g.Point[0:0]
|
||||
g.End = g.End[0:0]
|
||||
|
@ -537,9 +537,9 @@ func (g *Glyph) Load(f *Font, i Index) os.Error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NewGlyph returns a newly allocated Glyph.
|
||||
func NewGlyph() *Glyph {
|
||||
g := new(Glyph)
|
||||
// NewGlyphBuf returns a newly allocated GlyphBuf.
|
||||
func NewGlyphBuf() *GlyphBuf {
|
||||
g := new(GlyphBuf)
|
||||
g.Point = make([]Point, 0, 256)
|
||||
g.End = make([]int, 0, 32)
|
||||
return g
|
||||
|
|
Loading…
Reference in a new issue