mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 03:38:55 +01:00
text: improve rendering quality with HintingVertical
When HintingVertical is used, the interval between two glyphs is not quantized (i.e. not a whole pixel). The text package didn't consider this situation. This change improves the quality by using more various glyph images with 1/4 pixels granularity in vertical direction. Closes #2469
This commit is contained in:
parent
56788cf8d9
commit
a042af98b1
@ -46,7 +46,7 @@ func init() {
|
|||||||
mplusSmallFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
mplusSmallFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 24,
|
Size: 24,
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingVertical,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -54,7 +54,7 @@ func init() {
|
|||||||
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 32,
|
Size: 32,
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingVertical,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -62,7 +62,7 @@ func init() {
|
|||||||
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 48,
|
Size: 48,
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingVertical,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -96,7 +96,7 @@ func init() {
|
|||||||
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 24,
|
Size: 24,
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingVertical,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -104,7 +104,7 @@ func init() {
|
|||||||
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 48,
|
Size: 48,
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingFull, // Use quantization to save glyph cache images.
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -58,7 +58,7 @@ func initFont() {
|
|||||||
mplusFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
mplusFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 12 * ebiten.DeviceScaleFactor(),
|
Size: 12 * ebiten.DeviceScaleFactor(),
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingVertical,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -53,7 +53,7 @@ func init() {
|
|||||||
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
mplusNormalFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 24,
|
Size: 24,
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingVertical,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -61,7 +61,7 @@ func init() {
|
|||||||
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
mplusBigFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 32,
|
Size: 32,
|
||||||
DPI: dpi,
|
DPI: dpi,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingVertical,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -57,7 +57,7 @@ func init() {
|
|||||||
uiFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
uiFont, err = opentype.NewFace(tt, &opentype.FaceOptions{
|
||||||
Size: 12,
|
Size: 12,
|
||||||
DPI: 72,
|
DPI: 72,
|
||||||
Hinting: font.HintingFull,
|
Hinting: font.HintingVertical,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
41
text/text.go
41
text/text.go
@ -48,6 +48,10 @@ func fixed26_6ToFloat64(x fixed.Int26_6) float64 {
|
|||||||
return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
|
return float64(x>>6) + float64(x&((1<<6)-1))/float64(1<<6)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func adjustOffsetGranularity(x fixed.Int26_6) fixed.Int26_6 {
|
||||||
|
return x / (1 << 4) * (1 << 4)
|
||||||
|
}
|
||||||
|
|
||||||
func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *ebiten.Image, topleft fixed.Point26_6, op *ebiten.DrawImageOptions) {
|
func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *ebiten.Image, topleft fixed.Point26_6, op *ebiten.DrawImageOptions) {
|
||||||
if img == nil {
|
if img == nil {
|
||||||
return
|
return
|
||||||
@ -83,21 +87,30 @@ func getGlyphBounds(face font.Face, r rune) fixed.Rectangle26_6 {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type glyphImageCacheKey struct {
|
||||||
|
rune rune
|
||||||
|
xoffset fixed.Int26_6
|
||||||
|
}
|
||||||
|
|
||||||
type glyphImageCacheEntry struct {
|
type glyphImageCacheEntry struct {
|
||||||
image *ebiten.Image
|
image *ebiten.Image
|
||||||
atime int64
|
atime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
glyphImageCache = map[font.Face]map[rune]*glyphImageCacheEntry{}
|
glyphImageCache = map[font.Face]map[glyphImageCacheKey]*glyphImageCacheEntry{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func getGlyphImage(face font.Face, r rune, offset fixed.Point26_6) *ebiten.Image {
|
func getGlyphImage(face font.Face, r rune, offset fixed.Point26_6) *ebiten.Image {
|
||||||
if _, ok := glyphImageCache[face]; !ok {
|
if _, ok := glyphImageCache[face]; !ok {
|
||||||
glyphImageCache[face] = map[rune]*glyphImageCacheEntry{}
|
glyphImageCache[face] = map[glyphImageCacheKey]*glyphImageCacheEntry{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e, ok := glyphImageCache[face][r]; ok {
|
key := glyphImageCacheKey{
|
||||||
|
rune: r,
|
||||||
|
xoffset: offset.X,
|
||||||
|
}
|
||||||
|
if e, ok := glyphImageCache[face][key]; ok {
|
||||||
e.atime = now()
|
e.atime = now()
|
||||||
return e.image
|
return e.image
|
||||||
}
|
}
|
||||||
@ -105,7 +118,7 @@ func getGlyphImage(face font.Face, r rune, offset fixed.Point26_6) *ebiten.Image
|
|||||||
b := getGlyphBounds(face, r)
|
b := getGlyphBounds(face, r)
|
||||||
w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
|
w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
|
||||||
if w == 0 || h == 0 {
|
if w == 0 || h == 0 {
|
||||||
glyphImageCache[face][r] = &glyphImageCacheEntry{
|
glyphImageCache[face][key] = &glyphImageCacheEntry{
|
||||||
image: nil,
|
image: nil,
|
||||||
atime: now(),
|
atime: now(),
|
||||||
}
|
}
|
||||||
@ -133,7 +146,7 @@ func getGlyphImage(face font.Face, r rune, offset fixed.Point26_6) *ebiten.Image
|
|||||||
d.DrawString(string(r))
|
d.DrawString(string(r))
|
||||||
|
|
||||||
img := ebiten.NewImageFromImage(rgba)
|
img := ebiten.NewImageFromImage(rgba)
|
||||||
glyphImageCache[face][r] = &glyphImageCacheEntry{
|
glyphImageCache[face][key] = &glyphImageCacheEntry{
|
||||||
image: img,
|
image: img,
|
||||||
atime: now(),
|
atime: now(),
|
||||||
}
|
}
|
||||||
@ -242,7 +255,7 @@ func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *eb
|
|||||||
// The current glyph images assume that they are rendered on integer positions so far.
|
// The current glyph images assume that they are rendered on integer positions so far.
|
||||||
b := getGlyphBounds(face, r)
|
b := getGlyphBounds(face, r)
|
||||||
offset := fixed.Point26_6{
|
offset := fixed.Point26_6{
|
||||||
X: b.Min.X & ((1 << 6) - 1),
|
X: (adjustOffsetGranularity(dx) + b.Min.X) & ((1 << 6) - 1),
|
||||||
Y: b.Min.Y & ((1 << 6) - 1),
|
Y: b.Min.Y & ((1 << 6) - 1),
|
||||||
}
|
}
|
||||||
img := getGlyphImage(face, r, offset)
|
img := getGlyphImage(face, r, offset)
|
||||||
@ -353,13 +366,24 @@ func CacheGlyphs(face font.Face, text string) {
|
|||||||
textM.Lock()
|
textM.Lock()
|
||||||
defer textM.Unlock()
|
defer textM.Unlock()
|
||||||
|
|
||||||
|
var dx fixed.Int26_6
|
||||||
|
prevR := rune(-1)
|
||||||
for _, r := range text {
|
for _, r := range text {
|
||||||
|
if prevR >= 0 {
|
||||||
|
dx += face.Kern(prevR, r)
|
||||||
|
}
|
||||||
|
if r == '\n' {
|
||||||
|
dx = 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
b := getGlyphBounds(face, r)
|
b := getGlyphBounds(face, r)
|
||||||
offset := fixed.Point26_6{
|
offset := fixed.Point26_6{
|
||||||
X: b.Min.X & ((1 << 6) - 1),
|
X: (adjustOffsetGranularity(dx) + b.Min.X) & ((1 << 6) - 1),
|
||||||
Y: b.Min.Y & ((1 << 6) - 1),
|
Y: b.Min.Y & ((1 << 6) - 1),
|
||||||
}
|
}
|
||||||
getGlyphImage(face, r, offset)
|
getGlyphImage(face, r, offset)
|
||||||
|
dx += glyphAdvance(face, r)
|
||||||
|
prevR = r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,7 +472,7 @@ func AppendGlyphs(glyphs []Glyph, face font.Face, text string) []Glyph {
|
|||||||
|
|
||||||
b := getGlyphBounds(face, r)
|
b := getGlyphBounds(face, r)
|
||||||
offset := fixed.Point26_6{
|
offset := fixed.Point26_6{
|
||||||
X: b.Min.X & ((1 << 6) - 1),
|
X: (adjustOffsetGranularity(pos.X) + b.Min.X) & ((1 << 6) - 1),
|
||||||
Y: b.Min.Y & ((1 << 6) - 1),
|
Y: b.Min.Y & ((1 << 6) - 1),
|
||||||
}
|
}
|
||||||
if img := getGlyphImage(face, r, offset); img != nil {
|
if img := getGlyphImage(face, r, offset); img != nil {
|
||||||
@ -463,7 +487,6 @@ func AppendGlyphs(glyphs []Glyph, face font.Face, text string) []Glyph {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
pos.X += glyphAdvance(face, r)
|
pos.X += glyphAdvance(face, r)
|
||||||
|
|
||||||
prevR = r
|
prevR = r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user