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