diff options
author | Roozbeh Pournader <roozbeh@google.com> | 2015-01-13 08:50:27 -0800 |
---|---|---|
committer | James Godfrey-Kittle <jamesgk@google.com> | 2015-04-16 12:16:25 -0700 |
commit | 36623f13d922461c897fe0ae1ff550a073385329 (patch) | |
tree | 9339ab434222da3393491673ea12090de381495b /third_party/freetype-py/examples/texture_font.py | |
parent | 9fc4898c2338e4b9bb028171f5f8492f01879fbe (diff) |
Add FreeType python bindings to third_party.
Diffstat (limited to 'third_party/freetype-py/examples/texture_font.py')
-rw-r--r-- | third_party/freetype-py/examples/texture_font.py | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/third_party/freetype-py/examples/texture_font.py b/third_party/freetype-py/examples/texture_font.py new file mode 100644 index 0000000..8abdd2e --- /dev/null +++ b/third_party/freetype-py/examples/texture_font.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# +# FreeType high-level python API - Copyright 2011 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.maxint + best_index = -1 + best_width = sys.maxint + 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 + set_lcd_filter(FT_LCD_FILTER_LIGHT) + + + 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) * 0x10000L), int((0.0) * 0x10000L), + int((0.0) * 0x10000L), int((1.0) * 0x10000L) ) + 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 |