2021SC@SDUSC山东大学软件学院软件工程应用与实践--Ebiten代码分析 源码分析(七)
2021SC@SDUSC
目录
- 一.概述
- 二.代码分析
一.概述
本文将对ebiten的文本渲染系统进行介绍,即如何将文本内容渲染在游戏屏幕上。该部分相关的函数方法定义在internal/text/text.go文件内。
二.代码分析
首先定义了一个单调时钟monotonicClock,其中,单调时钟是不能向后的时钟,并有一个now方法获取当前的monotonicClock,又有初始化方法将其利用挂钩类hook中的AppendHookOnBeforeUpdate方法将monotonicClock++挂载到主Update()方法执行之前,即每一帧执行之前将monotonicClock的值加一。代码如下:
var (monotonicClock int64
)func now() int64 {return monotonicClock
}func init() {hooks.AppendHookOnBeforeUpdate(func() error {monotonicClock++return nil})
}
接下来是画出字体的字形,相关代码如下:
func drawGlyph(dst *ebiten.Image, face font.Face, r rune, img *ebiten.Image, dx, dy fixed.Int26_6, op *ebiten.DrawImageOptions) {if img == nil {return}b := getGlyphBounds(face, r)op2 := &ebiten.DrawImageOptions{}if op != nil {*op2 = *op}op2.GeoM.Reset()op2.GeoM.Translate(float64((dx+b.Min.X)>>6), float64((dy+b.Min.Y)>>6))if op != nil {op2.GeoM.Concat(op.GeoM)}dst.DrawImage(img, op2)
}var (glyphBoundsCache = map[font.Face]map[rune]fixed.Rectangle26_6{}
)func getGlyphBounds(face font.Face, r rune) fixed.Rectangle26_6 {if _, ok := glyphBoundsCache[face]; !ok {glyphBoundsCache[face] = map[rune]fixed.Rectangle26_6{}}if b, ok := glyphBoundsCache[face][r]; ok {return b}b, _, _ := face.GlyphBounds(r)glyphBoundsCache[face][r] = breturn b
}
其中传入参数的face指的是一种字体,一般派生自.ttf文件,dst是当前图像,img是目标图像,dx,dy是x,y方向上的偏移量。
在执行过程中,首先判断目标图像是否为空,然后根据传入的字体face获取字形边界,接着根据偏移量和绘制图像选项对目标图像进行几何变换操作,最后将目标图像绘制在当前图像上。
下面是Draw方法:
func Draw(dst *ebiten.Image, text string, face font.Face, x, y int, clr color.Color) {cr, cg, cb, ca := clr.RGBA()if ca == 0 {return}op := &ebiten.DrawImageOptions{}op.GeoM.Translate(float64(x), float64(y))op.ColorM.Scale(float64(cr)/float64(ca), float64(cg)/float64(ca), float64(cb)/float64(ca), float64(ca)/0xffff)DrawWithOptions(dst, text, face, op)
}
DRAW在给定的当前图像dst上绘制给定的文本。
face是用于文本渲染的字体。
x,y表示‘点’(句点)位置。
表面如果给定的文本由单个字符“.”组成,它将定位在给定位置(x,y)。
注意,这不代表左上角位置。
clr为文本渲染的颜色。
用于渲染的字形以最近最少使用的方式进行缓存。
然后旧的字形可能会从缓存中被逐出。
由于缓存容量有限,不能保证绘制时给定的符文的所有字形都会被缓存。
缓存与CacheGlyphs共享。
从性能上看,可以在每一帧调用相同文本、相同面孔的Draw。
DrawWithOptions和CacheGlyphs的实现方式如下:
/Draw=通过(*ebiten.Image).ReplacePixels创建字形,必要时放入缓存。通过(*ebiten.Image).DrawImage绘制到目的地。
CacheGlyphs=通过(*ebiten.Image).ReplacePixels创建字形,必要时放入缓存。注意,传递的字体将由该包保留,并且永远不会被释放。且Draw是并发安全的。
同样的,Draw方法也有与DrawImage方法类似的opinion方法即:
func DrawWithOptions(dst *ebiten.Image, text string, face font.Face, options *ebiten.DrawImageOptions) {textM.Lock()defer textM.Unlock()var dx, dy fixed.Int26_6prevR := rune(-1)faceHeight := face.Metrics().Heightfor _, r := range text {if prevR >= 0 {dx += face.Kern(prevR, r)}if r == '\n' {dx = 0dy += faceHeightprevR = rune(-1)continue}img := getGlyphImage(face, r)drawGlyph(dst, face, r, img, dx, dy, options)dx += glyphAdvance(face, r)prevR = r}
DrawithOptions在给定的目标图像DST上绘制给定的文本。
face是用于文本渲染的字体。
op是绘制字形图像的选项。
原点为点(句点)位置。
注意原点不是DST的左上角位置。
默认字形颜色为While。OP的ColorM可调整颜色。
用于渲染的字形以最近最少使用的方式进行缓存,旧的字形可能会从缓存中被逐出。由于缓存容量有限,不能保证DrawWithOptions中给定的符文的所有字形都会被缓存。
缓存与CacheGlyphs共享。
调用DrawWithOptions时,从性能上看,每一帧都有相同的文本和相同的面孔,这是可以接受的。
注意,传递的字体将由该包保留,并且永远不会被释放。且DrawithOptions也是并发安全的。
func BoundString(face font.Face, text string) image.Rectangle {textM.Lock()defer textM.Unlock()m := face.Metrics()faceHeight := m.Heightfx, fy := fixed.I(0), fixed.I(0)prevR := rune(-1)var bounds fixed.Rectangle26_6for _, r := range text {if prevR >= 0 {fx += face.Kern(prevR, r)}if r == '\n' {fx = fixed.I(0)fy += faceHeightprevR = rune(-1)continue}b := getGlyphBounds(face, r)b.Min.X += fxb.Max.X += fxb.Min.Y += fyb.Max.Y += fybounds = bounds.Union(b)fx += glyphAdvance(face, r)prevR = r}return image.Rect(int(math.Floor(fixed26_6ToFloat64(bounds.Min.X))),int(math.Floor(fixed26_6ToFloat64(bounds.Min.Y))),int(math.Ceil(fixed26_6ToFloat64(bounds.Max.X))),int(math.Ceil(fixed26_6ToFloat64(bounds.Max.Y))),)
}
BoundString返回使用给定字体的给定字符串的测量大小。
此方法将返回Draw绘制的字符串的确切大小(以像素为单位)。
边界的原点表示点(句点)的位置。
这意味着如果文本由一个字符‘.’组成,则该点呈现在(0,0)处。
这与golang.org/x/image/font的边界字符串非常相似。
但此边界字符串计算实际渲染的面积时会考虑多行和空格字符。
face是用于文本渲染的字体。
text是正在测量的字符串。
注意,传递的字体将由该包保留,并且永远不会被释放。
func CacheGlyphs(face font.Face, text string) {textM.Lock()defer textM.Unlock()for _, r := range text {getGlyphImage(face, r)}
}
CacheGlyphs将给定文本和给定字体的字形预先缓存到缓存中。
用于渲染的字形以最近最少使用的方式进行缓存。
然后旧的字形可能会从缓存中被逐出。
由于缓存容量有限,不能保证缓存CacheGlyphs上给定的符文的所有字形缓存与DRAW共享。
Draw会自动创建并缓存必要的字形,所以通常不需要调用CacheGlyphs。
但是例如当您为一个大文本的每个符文调用Draw时,Draw会尝试创建字形为每个字符缓存并渲染它。这是非常低效的,因为创建字形图像并渲染它,操作不同((*ebiten.Image).ReplacePixels和(*ebiten.Image).DrawImage),不能合并为一次抽签调用。
CacheGlyphs创建必要的字形而不呈现它们,因此这些操作很可能无论文本大小如何,都合并到一个绘制调用中。但如果符文的字形已经缓存,则CacheGlyphs不会对符文执行任何操作。
func FaceWithLineHeight(face font.Face, lineHeight float64) font.Face {return faceWithLineHeight{face: face,lineHeight: fixed.Int26_6(lineHeight * (1 << 6)),}
}type faceWithLineHeight struct {face font.FacelineHeight fixed.Int26_6
}
FaceWithLineHeight返回一个字体。Face使用给定的lineHeight(以像素为单位)。否则返回的Face将与Face具有相同的字形和度量。
同时,faceWithLineHeight还定义了一系列方法,这些方法都是调用的faceWithLineHeight的子属性face的同名方法,代码如下:
func (f faceWithLineHeight) Close() error {return f.face.Close()
}func (f faceWithLineHeight) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {return f.face.Glyph(dot, r)
}func (f faceWithLineHeight) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {return f.face.GlyphBounds(r)
}func (f faceWithLineHeight) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {return f.face.GlyphAdvance(r)
}func (f faceWithLineHeight) Kern(r0, r1 rune) fixed.Int26_6 {return f.face.Kern(r0, r1)
}func (f faceWithLineHeight) Metrics() font.Metrics {m := f.face.Metrics()m.Height = f.lineHeightreturn m
}
face类型是go语言原生的字体类,在此就不过多赘述了。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
