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 package main
import ( import (
"fmt"
"image" "image"
"image/draw" "image/draw"
"image/png" "image/png"
"math"
"os" "os"
"unicode" "unicode"
@ -12,6 +12,7 @@ import (
"github.com/bloeys/nmage/input" "github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/renderer/rend3dgl" "github.com/bloeys/nmage/renderer/rend3dgl"
nmageimgui "github.com/bloeys/nmage/ui/imgui" nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/bloeys/nterm/assert"
"github.com/golang/freetype/truetype" "github.com/golang/freetype/truetype"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
"golang.org/x/image/font" "golang.org/x/image/font"
@ -56,36 +57,103 @@ func (p *program) Init() {
panic("Failed to parse font. Err: " + err.Error()) panic("Failed to parse font. Err: " + err.Error())
} }
size := 40 pointSize := 40
face := truetype.NewFace(f, &truetype.Options{Size: float64(size), DPI: 72}) face := truetype.NewFace(f, &truetype.Options{Size: float64(pointSize), DPI: 72})
imgFromText("Hello there my friend", size, face, "./text.png") genTextureAtlas(f, face, pointSize)
fmt.Println(string(getGlyphs(loadFontRanges(f))))
} }
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 type FontTexAtlasGlyph struct {
rgbaDest := image.NewRGBA(image.Rect(0, 0, 640, 480)) U float32
draw.Draw(rgbaDest, rgbaDest.Bounds(), image.White, image.Point{}, draw.Src) 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{ drawer := &font.Drawer{
Dst: rgbaDest, Dst: atlas.Img,
Src: image.Black, Src: image.Black,
Face: face, Face: face,
} }
drawer.Dot = fixed.P(0, textSize) //Put glyphs on atlas
drawer.DrawString(text) 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) outFile, err := os.Create(file)
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer outFile.Close() defer outFile.Close()
err = png.Encode(outFile, rgbaDest) err = png.Encode(outFile, img)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -130,8 +198,8 @@ func (p *program) Deinit() {
} }
//loadFontRanges returns a list of ranges, each range is: [i][0]<=range<[i][1] //getGlyphRanges returns a list of ranges, each range is: [i][0]<=range<[i][1]
func loadFontRanges(f *truetype.Font) (ret [][2]rune) { func getGlyphRanges(f *truetype.Font) (ret [][2]rune) {
rr := [2]rune{-1, -1} rr := [2]rune{-1, -1}
for r := rune(0); r <= unicode.MaxRune; r++ { for r := rune(0); r <= unicode.MaxRune; r++ {
if privateUseArea(r) { if privateUseArea(r) {
@ -161,8 +229,8 @@ func privateUseArea(r rune) bool {
0x100000 <= r && r <= 0x10fffd 0x100000 <= r && r <= 0x10fffd
} }
//getGlyphs takes ranges of runes and produces an array of all the runes in these ranges //getGlyphsFromRanges takes ranges of runes and produces an array of all the runes in these ranges
func getGlyphs(ranges [][2]rune) []rune { func getGlyphsFromRanges(ranges [][2]rune) []rune {
out := make([]rune, 0) out := make([]rune, 0)
for _, rr := range ranges { for _, rr := range ranges {