Freetype-Go: batch Spans when painting.
Introduce MonochromePainter and GammaCorrectionPainter. R=r, rsc CC=golang-dev http://codereview.appspot.com/904041
This commit is contained in:
parent
593a182c4e
commit
4aa7375072
6 changed files with 266 additions and 55 deletions
|
@ -14,6 +14,7 @@ syntax:glob
|
|||
core
|
||||
_obj
|
||||
_test
|
||||
out.png
|
||||
|
||||
syntax:regexp
|
||||
^.*/core.[0-9]*$
|
||||
|
|
84
example/gamma/main.go
Normal file
84
example/gamma/main.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
// 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.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"exp/draw"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"freetype-go.googlecode.com/hg/freetype/raster"
|
||||
)
|
||||
|
||||
func p(x, y int) raster.Point {
|
||||
return raster.Point{raster.Fixed(x * 256), raster.Fixed(y * 256)}
|
||||
}
|
||||
|
||||
func clear(m *image.Alpha) {
|
||||
for y := 0; y < m.Height(); y++ {
|
||||
for x := 0; x < m.Width(); x++ {
|
||||
m.Pixel[y][x] = image.AlphaColor{0}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Draw a rounded corner that is one pixel wide.
|
||||
r := raster.New(50, 50)
|
||||
r.Start(p(5, 5))
|
||||
r.Add1(p(5, 25))
|
||||
r.Add2(p(5, 45), p(25, 45))
|
||||
r.Add1(p(45, 45))
|
||||
r.Add1(p(45, 44))
|
||||
r.Add1(p(26, 44))
|
||||
r.Add2(p(6, 44), p(6, 24))
|
||||
r.Add1(p(6, 5))
|
||||
r.Add1(p(5, 5))
|
||||
|
||||
// Rasterize that curve multiple times at different gammas.
|
||||
const (
|
||||
w = 600
|
||||
h = 200
|
||||
)
|
||||
rgba := image.NewRGBA(w, h)
|
||||
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)
|
||||
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))
|
||||
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
|
||||
draw.DrawMask(rgba, draw.Rect(x, y, x+50, y+50), image.Black, draw.ZP, mask, draw.ZP, draw.Over)
|
||||
}
|
||||
|
||||
// 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.")
|
||||
}
|
|
@ -155,19 +155,19 @@ func main() {
|
|||
f, err := os.Open("out.png", os.O_CREAT|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
return
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
b := bufio.NewWriter(f)
|
||||
err = png.Encode(b, rgba)
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
return
|
||||
os.Exit(1)
|
||||
}
|
||||
err = b.Flush()
|
||||
if err != nil {
|
||||
log.Stderr(err)
|
||||
return
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Wrote out.png OK.")
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ include $(GOROOT)/src/Make.$(GOARCH)
|
|||
|
||||
TARG=freetype-go.googlecode.com/hg/freetype/raster
|
||||
GOFILES=\
|
||||
paint.go\
|
||||
raster.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
|
|
161
freetype/raster/paint.go
Normal file
161
freetype/raster/paint.go
Normal file
|
@ -0,0 +1,161 @@
|
|||
// 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.
|
||||
|
||||
package raster
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
)
|
||||
|
||||
// A Span is a horizontal segment of pixels with constant alpha. X0 is an
|
||||
// inclusive bound and X1 is exclusive, the same as for slices. A fully
|
||||
// opaque Span has A == 1<<32 - 1.
|
||||
type Span struct {
|
||||
Y, X0, X1 int
|
||||
A uint32
|
||||
}
|
||||
|
||||
// A Painter knows how to paint a batch of Spans. A Span's alpha is non-zero
|
||||
// until the final Span of the rasterization, which is the zero value Span.
|
||||
// A rasterization may involve Painting multiple batches, but the final zero
|
||||
// value Span will occur only once per rasterization, not once per Paint call.
|
||||
// Paint may use all of ss as scratch space during the call.
|
||||
type Painter interface {
|
||||
Paint(ss []Span)
|
||||
}
|
||||
|
||||
// The PainterFunc type adapts an ordinary function to the Painter interface.
|
||||
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 {
|
||||
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)}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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 {
|
||||
color := image.AlphaColor{uint8(s.A >> 24)}
|
||||
p := m.Pixel[s.Y]
|
||||
for i := s.X0; i < s.X1; i++ {
|
||||
p[i] = color
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// A monochromePainter has a wrapped painter and an accumulator for merging
|
||||
// adjacent opaque Spans.
|
||||
type monochromePainter struct {
|
||||
p 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) {
|
||||
// We compact the ss slice, discarding any Spans whose alpha quantizes to zero.
|
||||
j := 0
|
||||
for _, s := range ss {
|
||||
if s.A >= 1<<31 {
|
||||
if m.y == s.Y && m.x1 == s.X0 {
|
||||
m.x1 = s.X1
|
||||
} else {
|
||||
ss[j] = Span{m.y, m.x0, m.x1, 1<<32 - 1}
|
||||
j++
|
||||
m.y, m.x0, m.x1 = s.Y, s.X0, s.X1
|
||||
}
|
||||
|
||||
} else if s.A == 0 {
|
||||
// The final Span of a rasterization is a zero value. We flush
|
||||
// our accumulated Span and finish with a zero Span.
|
||||
ss[j] = Span{m.y, m.x0, m.x1, 1<<32 - 1}
|
||||
j++
|
||||
if j < len(ss) {
|
||||
ss[j] = Span{}
|
||||
j++
|
||||
m.p.Paint(ss[0:j])
|
||||
} else if j == len(ss) {
|
||||
m.p.Paint(ss)
|
||||
ss[0] = Span{}
|
||||
m.p.Paint(ss[0:1])
|
||||
} else {
|
||||
panic("unreachable")
|
||||
}
|
||||
// Reset the accumulator, so that this Painter can be re-used.
|
||||
m.y, m.x0, m.x1 = 0, 0, 0
|
||||
return
|
||||
}
|
||||
}
|
||||
m.p.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}
|
||||
}
|
||||
|
||||
// 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.
|
||||
}
|
||||
|
||||
// Paint delegates to the wrapped Painter after performing gamma-correction
|
||||
// on each 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
|
||||
)
|
||||
for i, _ := range ss {
|
||||
if ss[i].A == 0 || ss[i].A == 1<<32-1 {
|
||||
continue
|
||||
}
|
||||
p, q := ss[i].A/M, (ss[i].A%M)>>9
|
||||
// The resultant alpha is a linear interpolation of g.a[p] and g.a[p+1].
|
||||
a := uint32(g.a[p])*(N-q) + uint32(g.a[p+1])*q
|
||||
a = (a + N/2) / N
|
||||
// Convert the alpha from 16-bit (which is g.a's range) to 32-bit.
|
||||
a |= a << 16
|
||||
// A non-final Span can't have zero alpha.
|
||||
if a == 0 {
|
||||
a = 1
|
||||
}
|
||||
ss[i].A = a
|
||||
}
|
||||
g.p.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}
|
||||
for i := 0; i < 256; i++ {
|
||||
a := float64(i) / 0xff
|
||||
a = math.Pow(a, gamma)
|
||||
g.a[i] = uint16(0xffff * a)
|
||||
}
|
||||
return g
|
||||
}
|
|
@ -17,28 +17,9 @@ package raster
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Painter knows how to paint a span, and a span is a horizontal segment
|
||||
// of a certain alpha. A fully opaque span has alpha == 1<<32-1. A span's
|
||||
// alpha is non-zero until the final Paint call of the rasterization, which
|
||||
// has all arguments zero.
|
||||
// TODO(nigeltao): Is it worth batching spans, so that Paint takes a []Span
|
||||
// instead of a single Span?
|
||||
type Painter interface {
|
||||
Paint(yi, xi0, xi1 int, alpha uint32)
|
||||
}
|
||||
|
||||
// The PainterFunc type adapts an ordinary function to the Painter interface.
|
||||
type PainterFunc func(yi, xi0, xi1 int, alpha uint32)
|
||||
|
||||
// Paint just delegates the call to f.
|
||||
func (f PainterFunc) Paint(yi, xi0, xi1 int, alpha uint32) {
|
||||
f(yi, xi0, xi1, alpha)
|
||||
}
|
||||
|
||||
// A 24.8 fixed point number.
|
||||
type Fixed int32
|
||||
|
||||
|
@ -103,6 +84,7 @@ type Rasterizer struct {
|
|||
// Buffers.
|
||||
cellBuf [256]cell
|
||||
cellIndexBuf [64]int
|
||||
spanBuf [64]Span
|
||||
}
|
||||
|
||||
// findCell returns the index in r.cell for the cell corresponding to
|
||||
|
@ -461,13 +443,13 @@ func (r *Rasterizer) areaToAlpha(area int) uint32 {
|
|||
return alpha
|
||||
}
|
||||
|
||||
// Rasterize converts r's accumulated curves into spans for p. The spans
|
||||
// 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 <= xi0 < xi1 <= r.width) and non-zero alpha,
|
||||
// except for the final span, which has yi, xi0, xi1 and alpha all equal
|
||||
// to zero.
|
||||
// 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
|
||||
for yi := 0; yi < len(r.cellIndex); yi++ {
|
||||
xi, cover := 0, 0
|
||||
for c := r.cellIndex[yi]; c != -1; c = r.cell[c].next {
|
||||
|
@ -482,7 +464,8 @@ func (r *Rasterizer) Rasterize(p Painter) {
|
|||
xi1 = r.width
|
||||
}
|
||||
if xi0 < xi1 {
|
||||
p.Paint(yi, xi0, xi1, alpha)
|
||||
r.spanBuf[s] = Span{yi, xi0, xi1, alpha}
|
||||
s++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -498,12 +481,19 @@ func (r *Rasterizer) Rasterize(p Painter) {
|
|||
xi1 = r.width
|
||||
}
|
||||
if xi0 < xi1 {
|
||||
p.Paint(yi, xi0, xi1, alpha)
|
||||
r.spanBuf[s] = Span{yi, xi0, xi1, alpha}
|
||||
s++
|
||||
}
|
||||
}
|
||||
if s > len(r.spanBuf)-2 {
|
||||
p.Paint(r.spanBuf[0:s])
|
||||
s = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
p.Paint(0, 0, 0, 0)
|
||||
r.spanBuf[s] = Span{}
|
||||
s++
|
||||
p.Paint(r.spanBuf[0:s])
|
||||
}
|
||||
|
||||
// Clear cancels any previous calls to r.Start or r.AddN.
|
||||
|
@ -554,29 +544,3 @@ func New(width, height int) *Rasterizer {
|
|||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// 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(yi, xi0, xi1 int, alpha uint32) {
|
||||
a := int(alpha >> 24)
|
||||
p := m.Pixel[yi]
|
||||
for i := xi0; i < xi1; i++ {
|
||||
ai := int(p[i].A)
|
||||
ai = (ai*255 + (255-ai)*a) / 255
|
||||
p[i] = image.AlphaColor{uint8(ai)}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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(yi, xi0, xi1 int, alpha uint32) {
|
||||
color := image.AlphaColor{uint8(alpha >> 24)}
|
||||
p := m.Pixel[yi]
|
||||
for i := xi0; i < xi1; i++ {
|
||||
p[i] = color
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue