mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
parent
f731c563e4
commit
88aeca7392
61
text/text.go
61
text/text.go
@ -47,16 +47,16 @@ const (
|
||||
cacheLimit = 512 // This is an arbitrary number.
|
||||
)
|
||||
|
||||
func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *glyphImage, x, y fixed.Int26_6, clr ebiten.ColorM) {
|
||||
func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *ebiten.Image, x, y fixed.Int26_6, clr ebiten.ColorM) {
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b := getGlyphBounds(face, r)
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Translate(fixed26_6ToFloat64(x+b.Min.X), fixed26_6ToFloat64(y+b.Min.Y))
|
||||
op.GeoM.Translate(fixed26_6ToFloat64((x + b.Min.X)), fixed26_6ToFloat64((y + b.Min.Y)))
|
||||
op.ColorM = clr
|
||||
_ = dst.DrawImage(img.image.SubImage(image.Rect(img.x, img.y, img.x+img.width, img.y+img.height)).(*ebiten.Image), op)
|
||||
_ = dst.DrawImage(img, op)
|
||||
}
|
||||
|
||||
var (
|
||||
@ -76,16 +76,8 @@ func getGlyphBounds(face font.Face, r rune) *fixed.Rectangle26_6 {
|
||||
return &b
|
||||
}
|
||||
|
||||
type glyphImage struct {
|
||||
image *ebiten.Image
|
||||
x int
|
||||
y int
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
type glyphImageCacheEntry struct {
|
||||
image *glyphImage
|
||||
image *ebiten.Image
|
||||
atime int64
|
||||
}
|
||||
|
||||
@ -94,7 +86,7 @@ var (
|
||||
emptyGlyphs = map[font.Face]map[rune]struct{}{}
|
||||
)
|
||||
|
||||
func getGlyphImages(face font.Face, runes []rune) []*glyphImage {
|
||||
func getGlyphImages(face font.Face, runes []rune) []*ebiten.Image {
|
||||
if _, ok := emptyGlyphs[face]; !ok {
|
||||
emptyGlyphs[face] = map[rune]struct{}{}
|
||||
}
|
||||
@ -102,7 +94,7 @@ func getGlyphImages(face font.Face, runes []rune) []*glyphImage {
|
||||
glyphImageCache[face] = map[rune]*glyphImageCacheEntry{}
|
||||
}
|
||||
|
||||
imgs := make([]*glyphImage, len(runes))
|
||||
imgs := make([]*ebiten.Image, len(runes))
|
||||
glyphBounds := map[rune]*fixed.Rectangle26_6{}
|
||||
neededGlyphIndices := map[int]rune{}
|
||||
for i, r := range runes {
|
||||
@ -141,54 +133,27 @@ func getGlyphImages(face font.Face, runes []rune) []*glyphImage {
|
||||
}
|
||||
|
||||
if len(neededGlyphIndices) > 0 {
|
||||
// TODO: What if w2 is too big (e.g. > 4096)?
|
||||
w2 := 0
|
||||
h2 := 0
|
||||
for _, b := range glyphBounds {
|
||||
for i, r := range neededGlyphIndices {
|
||||
b := glyphBounds[r]
|
||||
w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
|
||||
w2 += w
|
||||
if h2 < h {
|
||||
h2 = h
|
||||
}
|
||||
}
|
||||
rgba := image.NewRGBA(image.Rect(0, 0, w2, h2))
|
||||
|
||||
x := 0
|
||||
xs := map[rune]int{}
|
||||
for r, b := range glyphBounds {
|
||||
w := (b.Max.X - b.Min.X).Ceil()
|
||||
rgba := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
|
||||
d := font.Drawer{
|
||||
Dst: rgba,
|
||||
Src: image.White,
|
||||
Face: face,
|
||||
}
|
||||
d.Dot = fixed.Point26_6{X: fixed.I(x) - b.Min.X, Y: -b.Min.Y}
|
||||
d.Dot = fixed.Point26_6{X: -b.Min.X, Y: -b.Min.Y}
|
||||
d.DrawString(string(r))
|
||||
xs[r] = x
|
||||
|
||||
x += w
|
||||
}
|
||||
|
||||
img, _ := ebiten.NewImageFromImage(rgba, ebiten.FilterDefault)
|
||||
for i, r := range neededGlyphIndices {
|
||||
b := glyphBounds[r]
|
||||
w, h := (b.Max.X - b.Min.X).Ceil(), (b.Max.Y - b.Min.Y).Ceil()
|
||||
|
||||
g := &glyphImage{
|
||||
image: img,
|
||||
x: xs[r],
|
||||
y: 0,
|
||||
width: w,
|
||||
height: h,
|
||||
}
|
||||
img, _ := ebiten.NewImageFromImage(rgba, ebiten.FilterDefault)
|
||||
if _, ok := glyphImageCache[face][r]; !ok {
|
||||
glyphImageCache[face][r] = &glyphImageCacheEntry{
|
||||
image: g,
|
||||
image: img,
|
||||
atime: now(),
|
||||
}
|
||||
}
|
||||
imgs[i] = g
|
||||
imgs[i] = img
|
||||
}
|
||||
}
|
||||
return imgs
|
||||
|
@ -15,10 +15,13 @@
|
||||
package text_test
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"testing"
|
||||
|
||||
"github.com/hajimehoshi/bitmapfont"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
t "github.com/hajimehoshi/ebiten/internal/testing"
|
||||
@ -53,3 +56,93 @@ func TestTextColor(t *testing.T) {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
const testFaceSize = 6
|
||||
|
||||
type testFace struct{}
|
||||
|
||||
func (f *testFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
||||
dr = image.Rect(0, 0, testFaceSize, testFaceSize)
|
||||
a := image.NewAlpha(dr)
|
||||
switch r {
|
||||
case 'a':
|
||||
for j := 0; j < testFaceSize; j++ {
|
||||
for i := 0; i < testFaceSize; i++ {
|
||||
a.SetAlpha(i, j, color.Alpha{0x80})
|
||||
}
|
||||
}
|
||||
case 'b':
|
||||
for j := 0; j < testFaceSize; j++ {
|
||||
for i := 0; i < testFaceSize; i++ {
|
||||
a.SetAlpha(i, j, color.Alpha{0xff})
|
||||
}
|
||||
}
|
||||
}
|
||||
mask = a
|
||||
advance = fixed.I(testFaceSize)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func (f *testFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
bounds = fixed.R(0, 0, testFaceSize, testFaceSize)
|
||||
advance = fixed.I(testFaceSize)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func (f *testFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
return fixed.I(testFaceSize), true
|
||||
}
|
||||
|
||||
func (f *testFace) Kern(r0, r1 rune) fixed.Int26_6 {
|
||||
if r1 == 'b' {
|
||||
return fixed.I(-testFaceSize)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *testFace) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *testFace) Metrics() font.Metrics {
|
||||
return font.Metrics{
|
||||
Height: fixed.I(testFaceSize),
|
||||
Ascent: fixed.I(testFaceSize),
|
||||
Descent: 0,
|
||||
XHeight: 0,
|
||||
CapHeight: fixed.I(testFaceSize),
|
||||
CaretSlope: image.Pt(0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextOverlap(t *testing.T) {
|
||||
f := &testFace{}
|
||||
dst, _ := ebiten.NewImage(testFaceSize*2, testFaceSize, ebiten.FilterDefault)
|
||||
|
||||
// With testFace, 'b' is rendered at the previous position as 0xff.
|
||||
// 'a' is rendered at the current position as 0x80.
|
||||
Draw(dst, "ab", f, 0, 0, color.White)
|
||||
for j := 0; j < testFaceSize; j++ {
|
||||
for i := 0; i < testFaceSize; i++ {
|
||||
got := dst.At(i, j)
|
||||
want := color.RGBA{0xff, 0xff, 0xff, 0xff}
|
||||
if got != want {
|
||||
t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The glyph 'a' should be treated correctly.
|
||||
Draw(dst, "a", f, testFaceSize, 0, color.White)
|
||||
for j := 0; j < testFaceSize; j++ {
|
||||
for i := testFaceSize; i < testFaceSize*2; i++ {
|
||||
got := dst.At(i, j)
|
||||
want := color.RGBA{0x80, 0x80, 0x80, 0x80}
|
||||
if got != want {
|
||||
t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user