%PDF- %PDF-
| Direktori : /usr/share/doc/python3-freetype/examples/ |
| Current File : //usr/share/doc/python3-freetype/examples/texture_font.py |
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
#
# FreeType high-level python API - Copyright 2011-2015 Nicolas P. Rougier
# Distributed under the terms of the new BSD license.
#
# -----------------------------------------------------------------------------
'''
Texture font class
'''
import sys
import math
import numpy as np
import OpenGL.GL as gl
from freetype import *
class TextureAtlas:
'''
Group multiple small data regions into a larger texture.
The algorithm is based on the article by Jukka Jylänki : "A Thousand Ways
to Pack the Bin - A Practical Approach to Two-Dimensional Rectangle Bin
Packing", February 27, 2010. More precisely, this is an implementation of
the Skyline Bottom-Left algorithm based on C++ sources provided by Jukka
Jylänki at: http://clb.demon.fi/files/RectangleBinPack/
Example usage:
--------------
atlas = TextureAtlas(512,512,3)
region = atlas.get_region(20,20)
...
atlas.set_region(region, data)
'''
def __init__(self, width=1024, height=1024, depth=1):
'''
Initialize a new atlas of given size.
Parameters
----------
width : int
Width of the underlying texture
height : int
Height of the underlying texture
depth : 1 or 3
Depth of the underlying texture
'''
self.width = int(math.pow(2, int(math.log(width, 2) + 0.5)))
self.height = int(math.pow(2, int(math.log(height, 2) + 0.5)))
self.depth = depth
self.nodes = [ (0,0,self.width), ]
self.data = np.zeros((self.height, self.width, self.depth),
dtype=np.ubyte)
self.texid = 0
self.used = 0
def upload(self):
'''
Upload atlas data into video memory.
'''
if not self.texid:
self.texid = gl.glGenTextures(1)
gl.glBindTexture( gl.GL_TEXTURE_2D, self.texid )
gl.glTexParameteri( gl.GL_TEXTURE_2D,
gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP )
gl.glTexParameteri( gl.GL_TEXTURE_2D,
gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP )
gl.glTexParameteri( gl.GL_TEXTURE_2D,
gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR )
gl.glTexParameteri( gl.GL_TEXTURE_2D,
gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR )
if self.depth == 1:
gl.glTexImage2D( gl.GL_TEXTURE_2D, 0, gl.GL_ALPHA,
self.width, self.height, 0,
gl.GL_ALPHA, gl.GL_UNSIGNED_BYTE, self.data )
else:
gl.glTexImage2D( gl.GL_TEXTURE_2D, 0, gl.GL_RGB,
self.width, self.height, 0,
gl.GL_RGB, gl.GL_UNSIGNED_BYTE, self.data )
def set_region(self, region, data):
'''
Set a given region width provided data.
Parameters
----------
region : (int,int,int,int)
an allocated region (x,y,width,height)
data : numpy array
data to be copied into given region
'''
x, y, width, height = region
self.data[y:y+height,x:x+width, :] = data
def get_region(self, width, height):
'''
Get a free region of given size and allocate it
Parameters
----------
width : int
Width of region to allocate
height : int
Height of region to allocate
Return
------
A newly allocated region as (x,y,width,height) or (-1,-1,0,0)
'''
best_height = sys.maxsize
best_index = -1
best_width = sys.maxsize
region = 0, 0, width, height
for i in range(len(self.nodes)):
y = self.fit(i, width, height)
if y >= 0:
node = self.nodes[i]
if (y+height < best_height or
(y+height == best_height and node[2] < best_width)):
best_height = y+height
best_index = i
best_width = node[2]
region = node[0], y, width, height
if best_index == -1:
return -1,-1,0,0
node = region[0], region[1]+height, width
self.nodes.insert(best_index, node)
i = best_index+1
while i < len(self.nodes):
node = self.nodes[i]
prev_node = self.nodes[i-1]
if node[0] < prev_node[0]+prev_node[2]:
shrink = prev_node[0]+prev_node[2] - node[0]
x,y,w = self.nodes[i]
self.nodes[i] = x+shrink, y, w-shrink
if self.nodes[i][2] <= 0:
del self.nodes[i]
i -= 1
else:
break
else:
break
i += 1
self.merge()
self.used += width*height
return region
def fit(self, index, width, height):
'''
Test if region (width,height) fit into self.nodes[index]
Parameters
----------
index : int
Index of the internal node to be tested
width : int
Width or the region to be tested
height : int
Height or the region to be tested
'''
node = self.nodes[index]
x,y = node[0], node[1]
width_left = width
if x+width > self.width:
return -1
i = index
while width_left > 0:
node = self.nodes[i]
y = max(y, node[1])
if y+height > self.height:
return -1
width_left -= node[2]
i += 1
return y
def merge(self):
'''
Merge nodes
'''
i = 0
while i < len(self.nodes)-1:
node = self.nodes[i]
next_node = self.nodes[i+1]
if node[1] == next_node[1]:
self.nodes[i] = node[0], node[1], node[2]+next_node[2]
del self.nodes[i+1]
else:
i += 1
class TextureFont:
'''
A texture font gathers a set of glyph relatively to a given font filename
and size.
'''
def __init__(self, atlas, filename, size):
'''
Initialize font
Parameters:
-----------
atlas: TextureAtlas
Texture atlas where glyph texture will be stored
filename: str
Font filename
size : float
Font size
'''
self.atlas = atlas
self.filename = filename
self.size = size
self.glyphs = {}
face = Face( self.filename )
face.set_char_size( int(self.size*64))
self._dirty = False
metrics = face.size
self.ascender = metrics.ascender/64.0
self.descender = metrics.descender/64.0
self.height = metrics.height/64.0
self.linegap = self.height - self.ascender + self.descender
self.depth = atlas.depth
try:
set_lcd_filter(FT_LCD_FILTER_LIGHT)
except:
pass
def __getitem__(self, charcode):
'''
x.__getitem__(y) <==> x[y]
'''
if charcode not in self.glyphs.keys():
self.load('%c' % charcode)
return self.glyphs[charcode]
def get_texid(self):
'''
Get underlying texture identity .
'''
if self._dirty:
self.atlas.upload()
self._dirty = False
return self.atlas.texid
texid = property(get_texid,
doc='''Underlying texture identity.''')
def load(self, charcodes = ''):
'''
Build glyphs corresponding to individual characters in charcodes.
Parameters:
-----------
charcodes: [str | unicode]
Set of characters to be represented
'''
face = Face( self.filename )
pen = Vector(0,0)
hres = 16*72
hscale = 1.0/16
for charcode in charcodes:
face.set_char_size( int(self.size * 64), 0, hres, 72 )
matrix = Matrix( int((hscale) * 0x10000), int((0.0) * 0x10000),
int((0.0) * 0x10000), int((1.0) * 0x10000) )
face.set_transform( matrix, pen )
if charcode in self.glyphs.keys():
continue
self.dirty = True
flags = FT_LOAD_RENDER | FT_LOAD_FORCE_AUTOHINT
flags |= FT_LOAD_TARGET_LCD
face.load_char( charcode, flags )
bitmap = face.glyph.bitmap
left = face.glyph.bitmap_left
top = face.glyph.bitmap_top
width = face.glyph.bitmap.width
rows = face.glyph.bitmap.rows
pitch = face.glyph.bitmap.pitch
x,y,w,h = self.atlas.get_region(width/self.depth+2, rows+2)
if x < 0:
print ('Missed !')
continue
x,y = x+1, y+1
w,h = w-2, h-2
data = []
for i in range(rows):
data.extend(bitmap.buffer[i*pitch:i*pitch+width])
data = np.array(data,dtype=np.ubyte).reshape(h,w,3)
gamma = 1.5
Z = ((data/255.0)**(gamma))
data = (Z*255).astype(np.ubyte)
self.atlas.set_region((x,y,w,h), data)
# Build glyph
size = w,h
offset = left, top
advance= face.glyph.advance.x, face.glyph.advance.y
u0 = (x + 0.0)/float(self.atlas.width)
v0 = (y + 0.0)/float(self.atlas.height)
u1 = (x + w - 0.0)/float(self.atlas.width)
v1 = (y + h - 0.0)/float(self.atlas.height)
texcoords = (u0,v0,u1,v1)
glyph = TextureGlyph(charcode, size, offset, advance, texcoords)
self.glyphs[charcode] = glyph
# Generate kerning
for g in self.glyphs.values():
# 64 * 64 because of 26.6 encoding AND the transform matrix used
# in texture_font_load_face (hres = 64)
kerning = face.get_kerning(g.charcode, charcode, mode=FT_KERNING_UNFITTED)
if kerning.x != 0:
glyph.kerning[g.charcode] = kerning.x/(64.0*64.0)
kerning = face.get_kerning(charcode, g.charcode, mode=FT_KERNING_UNFITTED)
if kerning.x != 0:
g.kerning[charcode] = kerning.x/(64.0*64.0)
# High resolution advance.x calculation
# gindex = face.get_char_index( charcode )
# a = face.get_advance(gindex, FT_LOAD_RENDER | FT_LOAD_TARGET_LCD)/(64*72)
# glyph.advance = a, glyph.advance[1]
class TextureGlyph:
'''
A texture glyph gathers information relative to the size/offset/advance and
texture coordinates of a single character. It is generally built
automatically by a TextureFont.
'''
def __init__(self, charcode, size, offset, advance, texcoords):
'''
Build a new texture glyph
Parameter:
----------
charcode : char
Represented character
size: tuple of 2 ints
Glyph size in pixels
offset: tuple of 2 floats
Glyph offset relatively to anchor point
advance: tuple of 2 floats
Glyph advance
texcoords: tuple of 4 floats
Texture coordinates of bottom-left and top-right corner
'''
self.charcode = charcode
self.size = size
self.offset = offset
self.advance = advance
self.texcoords = texcoords
self.kerning = {}
def get_kerning(self, charcode):
''' Get kerning information
Parameters:
-----------
charcode: char
Character preceding this glyph
'''
if charcode in self.kerning.keys():
return self.kerning[charcode]
else:
return 0