Glyph texture atlas generation

This commit is contained in:
bloeys
2022-06-30 09:42:18 +04:00
parent a1a7f23a0a
commit a20894a76e

106
main.go
View File

@ -1,10 +1,10 @@
package main
import (
"fmt"
"image"
"image/draw"
"image/png"
"math"
"os"
"unicode"
@ -12,6 +12,7 @@ import (
"github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/renderer/rend3dgl"
nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/bloeys/nterm/assert"
"github.com/golang/freetype/truetype"
"github.com/veandco/go-sdl2/sdl"
"golang.org/x/image/font"
@ -56,36 +57,103 @@ func (p *program) Init() {
panic("Failed to parse font. Err: " + err.Error())
}
size := 40
face := truetype.NewFace(f, &truetype.Options{Size: float64(size), DPI: 72})
imgFromText("Hello there my friend", size, face, "./text.png")
fmt.Println(string(getGlyphs(loadFontRanges(f))))
pointSize := 40
face := truetype.NewFace(f, &truetype.Options{Size: float64(pointSize), DPI: 72})
genTextureAtlas(f, face, pointSize)
}
func imgFromText(text string, textSize int, face font.Face, file string) {
type FontTexAtlas struct {
Img *image.RGBA
Glyphs map[rune]FontTexAtlasGlyph
}
//Create a white image
rgbaDest := image.NewRGBA(image.Rect(0, 0, 640, 480))
draw.Draw(rgbaDest, rgbaDest.Bounds(), image.White, image.Point{}, draw.Src)
type FontTexAtlasGlyph struct {
U float32
V float32
}
func genTextureAtlas(f *truetype.Font, face font.Face, textSize int) *FontTexAtlas {
const maxAtlasSize = 8192
glyphs := getGlyphsFromRanges(getGlyphRanges(f))
assert.T(len(glyphs) > 0, "no glyphs")
//Choose atlas size
atlasSizeX := 512
atlasSizeY := 512
_, charWidthFixed, _ := face.GlyphBounds(glyphs[0])
charWidth := charWidthFixed.Round()
lineHeight := face.Metrics().Height.Round()
maxLinesInAtlas := atlasSizeY/lineHeight - 1
charsPerLine := atlasSizeX / charWidth
linesNeeded := int(math.Ceil(float64(len(glyphs)) / float64(charsPerLine)))
for linesNeeded > maxLinesInAtlas {
atlasSizeX *= 2
atlasSizeY *= 2
maxLinesInAtlas = atlasSizeY/lineHeight - 1
charsPerLine = atlasSizeX / charWidth
linesNeeded = int(math.Ceil(float64(len(glyphs)) / float64(charsPerLine)))
}
assert.T(atlasSizeX <= maxAtlasSize, "Atlas size went beyond maximum")
//Create atlas
atlas := &FontTexAtlas{
Img: image.NewRGBA(image.Rect(0, 0, atlasSizeX, atlasSizeY)),
Glyphs: make(map[rune]FontTexAtlasGlyph, len(glyphs)),
}
//Clear img to white
draw.Draw(atlas.Img, atlas.Img.Bounds(), image.White, image.Point{}, draw.Src)
//Draw black text on image
drawer := &font.Drawer{
Dst: rgbaDest,
Dst: atlas.Img,
Src: image.Black,
Face: face,
}
drawer.Dot = fixed.P(0, textSize)
drawer.DrawString(text)
//Put glyphs on atlas
charsOnLine := 0
lineDx := fixed.P(0, lineHeight)
drawer.Dot = fixed.P(0, lineHeight)
for _, g := range glyphs {
atlas.Glyphs[g] = FontTexAtlasGlyph{
U: float32(drawer.Dot.X.Floor()) / float32(atlasSizeX),
V: (float32(atlasSizeY-drawer.Dot.Y.Floor()) / float32(atlasSizeY)),
}
drawer.DrawString(string(g))
charsOnLine++
if charsOnLine == charsPerLine {
charsOnLine = 0
drawer.Dot.X = 0
drawer.Dot = drawer.Dot.Add(lineDx)
}
}
// fmt.Println(atlas.Glyphs)
saveImgToDisk(atlas.Img, "atlas.png")
return atlas
}
func saveImgToDisk(img image.Image, file string) {
// Save that RGBA image to disk.
outFile, err := os.Create(file)
if err != nil {
panic(err)
}
defer outFile.Close()
err = png.Encode(outFile, rgbaDest)
err = png.Encode(outFile, img)
if err != nil {
panic(err)
}
@ -130,8 +198,8 @@ func (p *program) Deinit() {
}
//loadFontRanges returns a list of ranges, each range is: [i][0]<=range<[i][1]
func loadFontRanges(f *truetype.Font) (ret [][2]rune) {
//getGlyphRanges returns a list of ranges, each range is: [i][0]<=range<[i][1]
func getGlyphRanges(f *truetype.Font) (ret [][2]rune) {
rr := [2]rune{-1, -1}
for r := rune(0); r <= unicode.MaxRune; r++ {
if privateUseArea(r) {
@ -161,8 +229,8 @@ func privateUseArea(r rune) bool {
0x100000 <= r && r <= 0x10fffd
}
//getGlyphs takes ranges of runes and produces an array of all the runes in these ranges
func getGlyphs(ranges [][2]rune) []rune {
//getGlyphsFromRanges takes ranges of runes and produces an array of all the runes in these ranges
func getGlyphsFromRanges(ranges [][2]rune) []rune {
out := make([]rune, 0)
for _, rr := range ranges {