PyOpenGL利用文泉驿正黑字体显示中文字体
摘要:在NeHe的OpenGL教程第43课源代码基础上,调用文泉驿正黑字体实现中文字体的显示
在OpenGL中显示汉字一直是个麻烦的事情,很多中文书籍的文抄公乐此不疲地介绍各种方法及其在windows下的代码实现。此处不在赘述,有兴趣的可以参考下面的文章:
OpenGL点阵字体绘制终极解决方案!哈!
下面的代码是在NeHe教程第43课的基础上,添加了中文字体显示功能,原则上只要字体库支持,任何unicode字符串都是可以显示的
btw,unbutu下字体库文件位置:/usr/share/fonts/
16/09/14 晚补注:
在Windos下请安装PIL的替代产品pillow并相应修改
import ImageFont
为
from PIL import ImageFont
python代码:
1 #! /usr/bin/env python 2 #coding=utf-8 3 # Modified from following code by T.Q. 2014 4 # A quick and simple opengl font library that uses GNU freetype2, written 5 # and distributed as part of a tutorial for nehe.gamedev.net. 6 # Sven Olsen, 2003 7 # Translated to PyOpenGL by Brian Leair, 2004 8 # 9 # 10 import ImageFont 11 import numpy as np 12 from OpenGL import GL,GLU 13 14 def is_cjk(uchar): 15 ''' 16 Checks for an unicode whether a CJK character 17 ''' 18 # cjk = (u'\u4e00',u'\u9fa5') 19 cjk = (u'\u2e80',u'\ufe4f') 20 if cjk[0]<=uchar<=cjk[1]: 21 return True 22 else: 23 return False 24 25 def is_ascii(uchar): 26 ''' 27 Checks for an unicode whether a ASCII character 28 ''' 29 ascii = (u'\u0000',u'\u00ff') 30 if ascii[0]<=uchar<=ascii[1]: 31 return True 32 else: 33 return False 34 35 def is_other(uchar): 36 ''' 37 Checks for an unicode whether an ASCII or CJK character 38 ''' 39 if not (is_cjk(uchar) or is_ascii(uchar)): 40 return True 41 else: 42 return False 43 44 def nextpow2(x): 45 ''' 46 If num isn't a power of 2, will return the next higher power of two 47 ''' 48 if x>=1.0: 49 return np.int(2**np.ceil(np.log2(x))) 50 else: 51 print "cannot convert negetive float to integer:",x 52 53 def getCharData(ft,uchar): 54 ''' 55 ''' 56 # Use our helper function to get the widths of 57 # the bitmap data that we will need in order to create 58 # our texture. 59 60 if isinstance(uchar,int): 61 glyph = ft.getmask(chr(uchar)) 62 elif isinstance(uchar,unicode): 63 if is_other(uchar): 64 return [None]*5 65 else: 66 glyph = ft.getmask(uchar) 67 elif isinstance(uchar,str): 68 uchar = unicode(uchar) 69 if is_other(uchar): 70 return [None]*5 71 else: 72 glyph = ft.getmask(uchar) 73 else: 74 return [None]*5 75 glyph_width,glyph_height = glyph.size 76 # We are using PIL's wrapping for FreeType. As a result, we don't have 77 # direct access to glyph.advance or other attributes, so we add a 1 pixel pad. 78 width = nextpow2(glyph_width + 1) 79 height = nextpow2(glyph_height + 1) 80 # python GL will accept lists of integers or strings, but not Numeric arrays 81 # so, we buildup a string for our glyph's texture from the Numeric bitmap 82 83 # Here we fill in the data for the expanded bitmap. 84 # Notice that we are using two channel bitmap (one for 85 # luminocity and one for alpha), but we assign 86 # both luminocity and alpha to the value that we 87 # find in the FreeType bitmap. 88 # We use the ?: operator so that value which we use 89 # will be 0 if we are in the padding zone, and whatever 90 # is the the Freetype bitmap otherwise. 91 expanded_data = "" 92 for j in xrange (height): 93 for i in xrange (width): 94 if (i >= glyph_width) or (j >= glyph_height): 95 value = chr(0) 96 expanded_data += value 97 expanded_data += value 98 else: 99 value = chr(glyph.getpixel((i, j))) 100 expanded_data += value 101 expanded_data += value 102 103 return glyph_width,glyph_height,width,height,expanded_data 104 105 def make_dlist(ft,ch,list_base,tex_base_list,color=[0,1,0]): 106 ''' 107 Given an integer char code, build a GL texture into texture_array, 108 build a GL display list for display list number display_list_base + ch. 109 Populate the glTexture for the integer ch and construct a display 110 list that renders the texture for ch. 111 Note, that display_list_base and texture_base are supposed 112 to be preallocated for 256 consecutive display lists and and 113 array of textures. 114 ''' 115 # Load char data 116 glyph_width,glyph_height,width,height,expanded_data = getCharData(ft,ch) 117 if not glyph_width: 118 return 119 # -------------- Build the gl texture ------------ 120 121 # Now we just setup some texture paramaters. 122 ID = GL.glGenTextures(1) 123 tex_base_list[ch] = ID 124 GL.glBindTexture(GL.GL_TEXTURE_2D, ID) 125 GL.glTexParameterf(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MAG_FILTER,GL.GL_LINEAR) 126 GL.glTexParameterf(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MIN_FILTER,GL.GL_LINEAR) 127 128 border = 0 129 # Here we actually create the texture itself, notice 130 # that we are using GL_LUMINANCE_ALPHA to indicate that 131 # we are using 2 channel data. 132 GL.glTexImage2D(GL.GL_TEXTURE_2D,0,GL.GL_RGBA,width,height,border, 133 GL.GL_LUMINANCE_ALPHA,GL.GL_UNSIGNED_BYTE,expanded_data) 134 135 # With the texture created, we don't need to expanded data anymore 136 expanded_data = None 137 138 139 140 # --- Build the gl display list that draws the texture for this character --- 141 142 # So now we can create the display list 143 GL.glNewList(list_base+ch,GL.GL_COMPILE) 144 145 if ch == ord(" "): 146 glyph_advance = glyph_width 147 GL.glTranslatef(glyph_advance, 0, 0) 148 GL.glEndList() 149 else: 150 GL.glBindTexture(GL.GL_TEXTURE_2D, ID) 151 GL.glPushMatrix() 152 153 # // first we need to move over a little so that 154 # // the character has the right amount of space 155 # // between it and the one before it. 156 # glyph_left = glyph.bbox [0] 157 # glTranslatef(glyph_left, 0, 0) 158 159 # // Now we move down a little in the case that the 160 # // bitmap extends past the bottom of the line 161 # // this is only true for characters like 'g' or 'y'. 162 # glyph_descent = glyph.decent 163 # glTranslatef(0, glyph_descent, 0) 164 165 # //Now we need to account for the fact that many of 166 # //our textures are filled with empty padding space. 167 # //We figure what portion of the texture is used by 168 # //the actual character and store that information in 169 # //the x and y variables, then when we draw the 170 # //quad, we will only reference the parts of the texture 171 # //that we contain the character itself. 172 x = np.float(glyph_width)/np.float(width) 173 y = np.float(glyph_height)/np.float(height) 174 175 # //Here we draw the texturemaped quads. 176 # //The bitmap that we got from FreeType was not 177 # //oriented quite like we would like it to be, 178 # //so we need to link the texture to the quad 179 # //so that the result will be properly aligned. 180 GL.glBegin(GL.GL_QUADS) 181 GL.glColor3fv(color) 182 GL.glTexCoord2f(0,0), GL.glVertex2f(0,glyph_height) 183 GL.glTexCoord2f(0,y), GL.glVertex2f(0,0) 184 GL.glTexCoord2f(x,y), GL.glVertex2f(glyph_width,0) 185 GL.glTexCoord2f(x,0), GL.glVertex2f(glyph_width, glyph_height) 186 GL.glEnd() 187 GL.glPopMatrix() 188 189 # Note, PIL's FreeType interface hides the advance from us. 190 # Normal PIL clients are rendering an entire string through FreeType, not 191 # a single character at a time like we are doing here. 192 # Because the advance value is hidden from we will advance 193 # the "pen" based upon the rendered glyph's width. This is imperfect. 194 GL.glTranslatef(glyph_width + 0.75, 0, 0) 195 196 # //increment the raster position as if we were a bitmap font. 197 # //(only needed if you want to calculate text length) 198 # //glBitmap(0,0,0,0,face->glyph->advance.x >> 6,0,NULL) 199 200 # //Finnish the display list 201 GL.glEndList() 202 return 203 204 def dispCJK(ft,uchar,tex_base_list,color=[1,1,0]): 205 ''' 206 ''' 207 # Load char data 208 glyph_width,glyph_height,width,height,expanded_data = getCharData(ft,uchar) 209 if glyph_width == None: 210 return 211 # -------------- Build the gl texture ------------ 212 213 # Now we just setup some texture paramaters. 214 ID = GL.glGenTextures(1) 215 tex_base_list.append(ID) 216 GL.glBindTexture(GL.GL_TEXTURE_2D, ID) 217 GL.glTexParameterf(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MAG_FILTER,GL.GL_LINEAR) 218 GL.glTexParameterf(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MIN_FILTER,GL.GL_LINEAR) 219 220 border = 0 221 # Here we actually create the texture itself, notice 222 # that we are using GL_LUMINANCE_ALPHA to indicate that 223 # we are using 2 channel data. 224 GL.glTexImage2D(GL.GL_TEXTURE_2D,0,GL.GL_RGBA,width,height,border, 225 GL.GL_LUMINANCE_ALPHA,GL.GL_UNSIGNED_BYTE,expanded_data) 226 227 # With the texture created, we don't need to expanded data anymore 228 expanded_data = None 229 GL.glBindTexture(GL.GL_TEXTURE_2D, ID) 230 GL.glPushMatrix() 231 x = np.float(glyph_width)/np.float(width) 232 y = np.float(glyph_height)/np.float(height) 233 GL.glBegin(GL.GL_QUADS) 234 GL.glColor3fv(color) 235 GL.glTexCoord2f(0,0), GL.glVertex2f(0,glyph_height) 236 GL.glTexCoord2f(0,y), GL.glVertex2f(0,0) 237 GL.glTexCoord2f(x,y), GL.glVertex2f(glyph_width,0) 238 GL.glTexCoord2f(x,0), GL.glVertex2f(glyph_width, glyph_height) 239 GL.glEnd() 240 GL.glPopMatrix() 241 GL.glTranslatef(glyph_width + 0.75, 0, 0) 242 return 243 244 245 def pushScreenCoordinateMatrix(): 246 # A fairly straight forward function that pushes 247 # a projection matrix that will make object world 248 # coordinates identical to window coordinates. 249 GL.glPushAttrib(GL.GL_TRANSFORM_BIT) 250 viewport = GL.glGetIntegerv(GL.GL_VIEWPORT) 251 GL.glMatrixMode(GL.GL_PROJECTION) 252 GL.glPushMatrix() 253 GL.glLoadIdentity() 254 GLU.gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]) 255 GL.glPopAttrib() 256 return 257 258 259 260 def pop_projection_matrix(): 261 # Pops the projection matrix without changing the current 262 # MatrixMode. 263 GL.glPushAttrib(GL.GL_TRANSFORM_BIT) 264 GL.glMatrixMode(GL.GL_PROJECTION) 265 GL.glPopMatrix() 266 GL.glPopAttrib() 267 return 268 269 270 class font_data(object): 271 ''' 272 ''' 273 def __init__(self, facename, pixel_height): 274 # We haven't yet allocated textures or display lists 275 self.m_allocated = False 276 self.m_font_height = pixel_height 277 self.m_facename = facename 278 279 # Try to obtain the FreeType font 280 try: 281 self.ft = ImageFont.truetype (facename, pixel_height) 282 except: 283 raise ValueError, "Unable to locate true type font '%s'" % (facename) 284 # Here we ask opengl to allocate resources for 285 # all the textures and displays lists which we 286 # are about to create. 287 # Note: only ASCII character 288 n = 256 289 self.m_list_base = GL.glGenLists(n) 290 291 # Consturct a list of 256 elements. This 292 # list will be assigned the texture IDs we create for each glyph 293 self.textures = [None] * n 294 self.cjk_textures = [] 295 # This is where we actually create each of the fonts display lists. 296 for i in xrange(n): 297 make_dlist(self.ft, i, self.m_list_base, self.textures) 298 299 self.m_allocated = True 300 301 def glPrint(self,x,y,string,color=[1,0,0]): 302 ''' 303 ''' 304 # We want a coordinate system where things coresponding to window pixels. 305 306 pushScreenCoordinateMatrix() 307 # //We make the height about 1.5* that of 308 h = np.float(self.m_font_height)/0.63 309 310 if not string: 311 pop_projection_matrix() 312 return 313 else: 314 if not isinstance(string,unicode): 315 try: 316 string = unicode(string) 317 except: 318 raise ValueError,"Can not convert to unicode",string 319 # //Here is some code to split the text that we have been 320 # //given into a set of lines. 321 # //This could be made much neater by using 322 # //a regular expression library such as the one avliable from 323 # //boost.org (I've only done it out by hand to avoid complicating 324 # //this tutorial with unnecessary library dependencies). 325 # //Note: python string object has convenience method for this :) 326 lines = string.split("\n") 327 GL.glPushAttrib(GL.GL_LIST_BIT|GL.GL_CURRENT_BIT |GL.GL_ENABLE_BIT|GL.GL_TRANSFORM_BIT) 328 GL.glMatrixMode(GL.GL_MODELVIEW) 329 # GL.glDisable(GL.GL_LIGHTING) 330 # GL.glEnable(GL.GL_TEXTURE_2D) 331 # GL.glDisable(GL.GL_DEPTH_TEST) 332 # GL.glEnable(GL.GL_BLEND) 333 # GL.glBlendFunc(GL.GL_SRC_ALPHA,GL.GL_ONE_MINUS_SRC_ALPHA) 334 335 GL.glListBase(self.m_list_base) 336 modelview_matrix = GL.glGetFloatv(GL.GL_MODELVIEW_MATRIX) 337 338 # //This is where the text display actually happens. 339 # //For each line of text we reset the modelview matrix 340 # //so that the line's text will start in the correct position. 341 # //Notice that we need to reset the matrix, rather than just translating 342 # //down by h. This is because when each character is 343 # //draw it modifies the current matrix so that the next character 344 # //will be drawn immediatly after it. 345 for i in xrange(len(lines)): 346 line = lines[i] 347 GL.glPushMatrix() 348 GL.glLoadIdentity () 349 GL.glTranslatef(x,y-h*i,0); 350 GL.glMultMatrixf(modelview_matrix); 351 352 # // The commented out raster position stuff can be useful if you need to 353 # // know the length of the text that you are creating. 354 # // If you decide to use it make sure to also uncomment the glBitmap command 355 # // in make_dlist(). 356 # // glRasterPos2f(0,0); 357 # glCallLists (line) 358 for tt in line: 359 if is_cjk(tt): 360 dispCJK(self.ft,tt,self.cjk_textures) 361 else: 362 # print 'ascii',tt 363 GL.glCallList(ord(tt)+1) 364 # // rpos = glGetFloatv (GL_CURRENT_RASTER_POSITION) 365 # // float len=x-rpos[0]; 366 GL.glPopMatrix() 367 GL.glPopAttrib() 368 pop_projection_matrix() 369 370 371 372 def release(self): 373 """ Release the gl resources for this Face. 374 (This provides the functionality of KillFont () and font_data::clean () 375 """ 376 if self.m_allocated: 377 # Free up the glTextures and the display lists for our face 378 GL.glDeleteLists( self.m_list_base, 256); 379 for ID in self.textures: 380 GL.glDeleteTextures(ID) 381 if self.cjk_textures: 382 for ID in self.cjk_textures: 383 GL.glDeleteTextures(ID) 384 # Extra defensive. Clients that continue to try and use this object 385 # will now trigger exceptions. 386 self.list_base = None 387 self.m_allocated = False 388 389 def __del__ (self): 390 """ Python destructor for when no more refs to this Face object """ 391 self.release() 392 393 # Unit Test harness if this python module is run directly. 394 if __name__=="__main__": 395 print "testing availability of freetype font arial\n" 396 ft = ImageFont.truetype ("Test.ttf", 15) 397 if ft: 398 print "Found the TrueType font 'Test.ttf'" 399 else: 400 print "faild to find the TrueTYpe font 'arial'\n" 401 402
1 #! /usr/bin/env python 2 #coding=utf-8 3 # NeHe Tutorial Lesson: 43 - FreeType fonts in OpenGL 4 # 5 # Ported to PyOpenGL 2.0 by Brian Leair 18 Jan 2004 6 # 7 # This code was created by Jeff Molofee 2000 8 # 9 # The port was based on the PyOpenGL tutorials and from 10 # PyOpenGLContext (tests/glprint.py) 11 # 12 # If you've found this code useful, feel free to let me know 13 # at (Brian Leair telcom_sage@yahoo.com). 14 # 15 # See original source and C based tutorial at http://nehe.gamedev.net 16 # 17 # Note: 18 # ----- 19 # This code is not an ideal example of Pythonic coding or use of OO 20 # techniques. It is a simple and direct exposition of how to use the 21 # Open GL API in Python via the PyOpenGL package. It also uses GLUT, 22 # a high quality platform independent library. Due to using these APIs, 23 # this code is more like a C program using procedural programming. 24 # 25 # To run this example you will need: 26 # Python - www.python.org (v 2.3 as of 1/2004) 27 # PyOpenGL - pyopengl.sourceforge.net (v 2.0.1.07 as of 1/2004) 28 # Numeric Python - (v.22 of "numpy" as of 1/2004) numpy.sourceforge.net 29 # Python Image Library - http://www.pythonware.com/products/pil/ (v1.1.4 or later) 30 # 31 # Make sure to get versions of Numeric, PyOpenGL, and PIL to match your 32 # version of python. 33 # 34 # 35 36 from OpenGL.GL import * 37 from OpenGL.GLUT import * 38 from OpenGL.GLU import * 39 40 # Imports specific to Lesson 43 41 #import glFreeType 42 import freeTypeFont as glFreeType 43 from math import cos 44 45 import sys 46 47 # Python 2.2 defines these directly 48 try: 49 True 50 except NameError: 51 True = 1==1 52 False = 1==0 53 54 55 # Some api in the chain is translating the keystrokes to this octal string 56 # so instead of saying: ESCAPE = 27, we use the following. 57 ESCAPE = '\033' 58 59 # Number of the glut window. 60 window = 0 61 62 our_font = None 63 64 # A general OpenGL initialization function. Sets all of the initial parameters. 65 def InitGL(Width, Height): # We call this right after our OpenGL window is created. 66 global our_font 67 glShadeModel(GL_SMOOTH) # Enables Smooth Color Shading 68 glClearColor(0.0, 0.0, 0.0, 0.5) # This Will Clear The Background Color To Black 69 glClearDepth(1.0) # Enables Clearing Of The Depth Buffer 70 glEnable(GL_DEPTH_TEST) # Enables Depth Testing 71 glEnable(GL_TEXTURE_2D) # Enables texture mapping 72 glDepthFunc(GL_LEQUAL) # The Type Of Depth Test To Do 73 glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) # Really Nice Perspective Calculations 74 75 # Currently omitting the wgl based font. See lesson13.py for example use of wgl font. 76 # FYI, the ttf font file "Test.ttf" in lesson43 is the typeface "Arial Black Italic". 77 # our_font = glFreeType.font_data ("ARBLI___.ttf", 16) 78 # our_font = glFreeType.font_data ("Test.ttf", 16) 79 our_font = glFreeType.font_data("wqy-zenhei.ttc",20) 80 return True 81 82 83 84 # The function called when our window is resized (which shouldn't happen if you enable fullscreen, below) 85 def ReSizeGLScene(Width, Height): 86 if Height == 0: # Prevent A Divide By Zero If The Window Is Too Small 87 Height = 1 88 89 glViewport(0, 0, Width, Height) # Reset The Current Viewport And Perspective Transformation 90 glMatrixMode(GL_PROJECTION) 91 glLoadIdentity() 92 # // field of view, aspect ratio, near and far 93 # This will squash and stretch our objects as the window is resized. 94 gluPerspective(45.0, float(Width)/float(Height), 0.1, 100.0) 95 96 glMatrixMode(GL_MODELVIEW) 97 glLoadIdentity() 98 99 cnt1 = 0 100 # The main drawing function. 101 def DrawGLScene(): 102 global cnt1 103 global our_font 104 105 # Clear The Screen And The Depth Buffer 106 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 107 glLoadIdentity() # Reset The View 108 # Step back (away from objects) 109 glTranslatef (0.0, 0.0, -1.0) 110 111 # Currently - NYI - No WGL text 112 # Blue Text 113 # glColor3ub(0, 0, 0xff) 114 # 115 # // Position The WGL Text On The Screen 116 # glRasterPos2f(-0.40f, 0.35f); 117 # glPrint("Active WGL Bitmap Text With NeHe - %7.2f", cnt1); 118 119 # Red Text 120 glColor3ub (0xff, 0, 0) 121 122 glPushMatrix () 123 glLoadIdentity () 124 # Spin the text, rotation around z axe == will appears as a 2d rotation of the text on our screen 125 glRotatef (cnt1, 0, 0, 1) 126 glScalef (1, 0.8 + 0.3* cos (cnt1/5), 1) 127 glTranslatef (-180, 0, 0) 128 our_font.glPrint(320, 240, u"Active123中文 \nFreeType Text 汉字- %7.2f\n{【丯丱丳丵饕餮】}、\n今日はとてもいい天気です。空は靑く" % (cnt1)) 129 glPopMatrix () 130 131 # //Uncomment this to test out print's ability to handle newlines. 132 # our_font.glPrint (320, 240, "Here\nthere\nbe\n\nnewlines %f\n." % (cnt1)) 133 134 cnt1 += 0.091 135 # cnt2 += 0.005 136 137 glutSwapBuffers() 138 return 139 140 141 # The function called whenever a key is pressed. Note the use of Python tuples to pass in: (key, x, y) 142 def keyPressed(*args): 143 global window 144 global our_font 145 # If escape is pressed, kill everything. 146 if args[0] == ESCAPE: 147 our_font.release () 148 sys.exit() 149 150 def main(): 151 global window 152 # pass arguments to init 153 glutInit(sys.argv) 154 155 # Select type of Display mode: 156 # Double buffer 157 # RGBA color 158 # Alpha components supported 159 # Depth buffer 160 glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH) 161 162 # get a 640 x 480 window 163 glutInitWindowSize(640, 480) 164 165 # the window starts at the upper left corner of the screen 166 glutInitWindowPosition(0, 0) 167 168 # Okay, like the C version we retain the window id to use when closing, but for those of you new 169 # to Python (like myself), remember this assignment would make the variable local and not global 170 # if it weren't for the global declaration at the start of main. 171 window = glutCreateWindow("NeHe & Sven Olsen's TrueType Font Tutorial") 172 173 # Register the drawing function with glut, BUT in Python land, at least using PyOpenGL, we need to 174 # set the function pointer and invoke a function to actually register the callback, otherwise it 175 # would be very much like the C version of the code. 176 glutDisplayFunc(DrawGLScene) 177 178 # Uncomment this line to get full screen. 179 #glutFullScreen() 180 181 # When we are doing nothing, redraw the scene. 182 glutIdleFunc(DrawGLScene) 183 184 # Register the function called when our window is resized. 185 glutReshapeFunc(ReSizeGLScene) 186 187 # Register the function called when the keyboard is pressed. 188 glutKeyboardFunc(keyPressed) 189 190 # Initialize our window. 191 InitGL(640, 480) 192 193 # Start Event Processing Engine 194 glutMainLoop() 195 196 # Print message to console, and kick off the main to get it rolling. 197 print "Hit ESC key to quit." 198 main()
Ubuntu 14.04
Win7