Question

I'm writting a simple bitmap font renderer in pySFML and wanted to ask is there a better and faster way to approach this problem.

I'm using VertexArray and create a quad for each character in a string. Each quad has appropriate texture coordinates applied.

Example font (PNG file): Bitmap font example

Font rendering code:

import sfml


class BitmapFont(object):
    '''
    Loads a bitmap font. 
    `chars` is string with all characters available in the font file, example: '+0123456789x'.
    `widths` is mapping between characters and character width in pixels.
    '''
    def __init__(self, path, chars, widths, colors=1, kerning=0):
        self.texture = sfml.Texture.from_file(path)
        self.colors = colors
        self.height = self.texture.height / self.colors
        self.chars = chars
        self.kerning = kerning
        self.widths = widths

        self.glyphs = []
        y = 0
        for color in range(self.colors):
            x = 0
            self.glyphs.append({})
            for char in self.chars:
                glyph_pos = x, y
                glyph_size = self.widths[char], self.height
                glyph = sfml.Rectangle(glyph_pos, glyph_size)
                self.glyphs[color][char] = glyph
                x += glyph.width
            y += self.height


class BitmapText(sfml.TransformableDrawable):
    '''Used to render text with `BitmapFonts`.'''

    def __init__(self, string='', font=None, color=0, align='left', position=(0, 0)):
        super().__init__()
        self.vertices = sfml.VertexArray(sfml.PrimitiveType.QUADS, 4)
        self.font = font
        self.color = color
        self._string = ''
        self.string = string
        self.position = position

    @property
    def string(self):
        return self._string

    @string.setter
    def string(self, value):
        '''Calculates new vertices each time string has changed.'''
        # This function is slowest and probably can be optimized.

        if value == self._string:
            return
        if len(value) != len(self._string):
            self.vertices.resize(4 * len(value))
        self._string = value
        x = 0
        y = 0
        vertices = self.vertices
        glyphs = self.font.glyphs[self.color]
        for i, char in enumerate(self._string):
            glyph = glyphs[char]
            p = i * 4
            vertices[p + 0].position = x, y
            vertices[p + 1].position = x + glyph.width, y
            vertices[p + 2].position = x + glyph.width, y + glyph.height
            vertices[p + 3].position = x, y + glyph.height
            vertices[p + 0].tex_coords = glyph.left, glyph.top
            vertices[p + 1].tex_coords = glyph.right, glyph.top
            vertices[p + 2].tex_coords = glyph.right, glyph.bottom
            vertices[p + 3].tex_coords = glyph.left, glyph.bottom
            x += glyph.width + self.font.kerning

    def draw(self, target, states):
        '''Draws whole string using texture from a font.'''
        states.texture = self.font.texture
        states.transform = self.transform
        target.draw(self.vertices, states)

Simple benchmark with FPS counter:

from random import random, randint

import sfml

from font import BitmapFont, BitmapText


font = sfml.Font.from_file('arial.ttf')

bitmap_font = BitmapFont('font.png', chars='-x+0123456789 ', kerning=-3,
                         widths={'x': 21, '+': 18, '0': 18, '1': 14, '2': 18, '3': 18, '4': 19, '5': 18, '6': 18,
                                 '7': 17, '8': 18, '9': 18, '-': 17, ' ': 8})

window = sfml.RenderWindow(sfml.VideoMode(960, 640), 'Font test')

fps_text = sfml.Text('', font, 18)
fps_text.position = 10, 10
fps_text.color = sfml.Color.WHITE

fps_text_shadow = sfml.Text('', font, 18)
fps_text_shadow.position = 12, 12
fps_text_shadow.color = sfml.Color.BLACK

frame = fps = frame_time = 0

clock = sfml.Clock()
texts = [BitmapText('x01234 56789', font=bitmap_font, color=randint(0, bitmap_font.colors - 1)) for i in range(1000)]

while window.is_open:
    for event in window.events:
        if type(event) is sfml.CloseEvent:
            window.close()

    time_delta = clock.restart().seconds
    if time_delta > .2:
        continue

    frame_time += time_delta
    if frame_time >= 1:
        fps = frame
        frame_time = frame = 0
        fps_text_shadow.string = fps_text.string = 'FPS: {fps}'.format(fps=fps)
    else:
        frame += 1

    window.clear(sfml.Color(63, 63, 63))

    for t in texts:
        t.position = random() * 960, random() * 640
        t.string = str(randint(0, 10000000))
        window.draw(t)

    window.draw(fps_text_shadow)
    window.draw(fps_text)
    window.display()

I'm using Python 3.3, pySFML 1.3, SFML 2.0 and Windows.

Was it helpful?

Solution

Laurent Gomila (author of SFML) confirmed in other forum, that my approach to bitmap fonts is same as vector fonts implementation in SFML (namely VertexArray and quad for each character).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top