mirror of
https://github.com/bloeys/nterm.git
synced 2025-12-29 14:38:19 +00:00
Glyph texture atlas generation
This commit is contained in:
106
main.go
106
main.go
@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user