text/msdf/generator.go
Functions
func Config
func (g *Generator) Config() Config {
return g.config
}
func DefaultGenerator
DefaultGenerator creates a new MSDF generator with default configuration.
func DefaultGenerator() *Generator {
return NewGenerator(DefaultConfig())
}
func ErrorCorrection
ErrorCorrection applies error correction to fix artifacts.
This handles cases where the median produces incorrect results.
func ErrorCorrection(msdf *MSDF, threshold float64) {
if msdf == nil {
return
}
w, h := msdf.Width, msdf.Height
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
r, g, b := msdf.GetPixel(x, y)
// Get the median
med := median3Byte(r, g, b)
// Calculate error for each channel
rErr := math.Abs(float64(r) - float64(med))
gErr := math.Abs(float64(g) - float64(med))
bErr := math.Abs(float64(b) - float64(med))
maxErr := max(rErr, gErr, bErr)
thresholdVal := threshold * 255
// If error is too high, correct by moving toward median
if maxErr > thresholdVal {
if rErr > thresholdVal {
r = correctChannel(r, med, thresholdVal)
}
if gErr > thresholdVal {
g = correctChannel(g, med, thresholdVal)
}
if bErr > thresholdVal {
b = correctChannel(b, med, thresholdVal)
}
msdf.SetPixel(x, y, r, g, b)
}
}
}
}
func Generate
Generate creates an MSDF texture from a glyph outline.
Returns nil if the outline is empty.
func (g *Generator) Generate(outline *text.GlyphOutline) (*MSDF, error) {
if err := g.config.Validate(); err != nil {
return nil, err
}
if outline == nil || outline.IsEmpty() {
// Return a simple transparent MSDF for empty glyphs
return g.generateEmpty(), nil
}
// Convert outline to shape with edges
shape := FromOutline(outline)
if shape.EdgeCount() == 0 {
return g.generateEmpty(), nil
}
// Assign colors to edges based on corners
AssignColors(shape, g.config.AngleThreshold)
// Calculate scaling and translation
shapeBounds := shape.Bounds
if shapeBounds.IsEmpty() {
return g.generateEmpty(), nil
}
// Add padding for the distance range
padding := g.config.Range
bounds := shapeBounds.Expand(padding)
// Calculate scale to fit in texture size
scale := calculateScale(bounds, g.config.Size, padding)
// Center the expanded bounds within the MSDF cell.
// With uniform scaling (min of scaleX, scaleY), the non-limiting axis
// doesn't fill the available space. Centering ensures the glyph content
// is at the center of the cell, which allows symmetric padding in the
// screen quad computation (gpu_text.go).
occupiedW := bounds.Width() * scale
occupiedH := bounds.Height() * scale
translateX := (float64(g.config.Size) - occupiedW) / 2
translateY := (float64(g.config.Size) - occupiedH) / 2
// Create the MSDF texture
msdf := &MSDF{
Data: make([]byte, g.config.Size*g.config.Size*3),
Width: g.config.Size,
Height: g.config.Size,
Bounds: bounds,
Scale: scale,
TranslateX: translateX,
TranslateY: translateY,
}
// Generate the distance field
g.generateDistanceField(msdf, shape)
return msdf, nil
}
func Generate
Generate generates an MSDF using a pooled generator.
func (p *GeneratorPool) Generate(outline *text.GlyphOutline) (*MSDF, error) {
gen := p.Get()
defer p.Put(gen)
return gen.Generate(outline)
}
func GenerateBatch
GenerateBatch generates MSDF textures for multiple outlines.
This is more efficient than generating them one by one.
func (g *Generator) GenerateBatch(outlines []*text.GlyphOutline) ([]*MSDF, error) {
if err := g.config.Validate(); err != nil {
return nil, err
}
results := make([]*MSDF, len(outlines))
var wg sync.WaitGroup
var firstError error
var errMu sync.Mutex
for i, outline := range outlines {
wg.Add(1)
go func(idx int, o *text.GlyphOutline) {
defer wg.Done()
msdf, err := g.Generate(o)
if err != nil {
errMu.Lock()
if firstError == nil {
firstError = err
}
errMu.Unlock()
return
}
results[idx] = msdf
}(i, outline)
}
wg.Wait()
if firstError != nil {
return nil, firstError
}
return results, nil
}
func GenerateWithMetrics
GenerateWithMetrics generates an MSDF and returns metrics.
func (g *Generator) GenerateWithMetrics(outline *text.GlyphOutline) (*MSDF, *Metrics, error) {
if err := g.config.Validate(); err != nil {
return nil, nil, err
}
if outline == nil || outline.IsEmpty() {
msdf := g.generateEmpty()
metrics := &Metrics{
Width: msdf.Width,
Height: msdf.Height,
Scale: msdf.Scale,
Bounds: msdf.Bounds,
NumContours: 0,
NumEdges: 0,
}
return msdf, metrics, nil
}
// Convert outline to shape
shape := FromOutline(outline)
// Collect metrics
metrics := &Metrics{
Width: g.config.Size,
Height: g.config.Size,
NumContours: len(shape.Contours),
NumEdges: shape.EdgeCount(),
Bounds: shape.Bounds,
}
// Generate the MSDF
msdf, err := g.Generate(outline)
if err != nil {
return nil, nil, err
}
metrics.Scale = msdf.Scale
return msdf, metrics, nil
}
func Get
Get retrieves a generator from the pool.
func (p *GeneratorPool) Get() *Generator {
return p.pool.Get().(*Generator)
}
func MedianFilter
MedianFilter applies a median filter to clean up noise.
This is a post-processing step that can improve quality.
func MedianFilter(msdf *MSDF) *MSDF {
if msdf == nil {
return nil
}
result := &MSDF{
Data: make([]byte, len(msdf.Data)),
Width: msdf.Width,
Height: msdf.Height,
Bounds: msdf.Bounds,
Scale: msdf.Scale,
TranslateX: msdf.TranslateX,
TranslateY: msdf.TranslateY,
}
w, h := msdf.Width, msdf.Height
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
rVals, gVals, bVals := collectNeighborhood(msdf, x, y, w, h)
rMed := median9(rVals)
gMed := median9(gVals)
bMed := median9(bVals)
result.SetPixel(x, y, rMed, gMed, bMed)
}
}
return result
}
func NewGenerator
NewGenerator creates a new MSDF generator with the given configuration.
func NewGenerator(config Config) *Generator {
return &Generator{
config: config,
}
}
func NewGeneratorPool
NewGeneratorPool creates a new generator pool with the given configuration.
func NewGeneratorPool(config Config) *GeneratorPool {
return &GeneratorPool{
config: config,
pool: sync.Pool{
New: func() interface{} {
return NewGenerator(config)
},
},
}
}
func Put
Put returns a generator to the pool.
func (p *GeneratorPool) Put(g *Generator) {
// Reset config in case it was modified
g.config = p.config
p.pool.Put(g)
}
func SetConfig
SetConfig updates the generator's configuration.
func (g *Generator) SetConfig(config Config) {
g.config = config
}
Structs
type Generator struct
Generator creates MSDF textures from glyph outlines.
type Generator struct {
config Config
}
type Metrics struct
Metrics returns statistics about the generated MSDF.
type Metrics struct {
// Width and Height of the texture.
Width, Height int
// Scale factor used.
Scale float64
// Bounds in outline space.
Bounds Rect
// NumContours is the number of contours in the source shape.
NumContours int
// NumEdges is the total number of edges.
NumEdges int
}
type GeneratorPool struct
GeneratorPool manages a pool of generators for concurrent use.
type GeneratorPool struct {
pool sync.Pool
config Config
}
Config returns the generator's configuration.