1 #!/usr/bin/env python
\r
4 # Provides some text display functions for wx + ogl
\r
5 # Copyright (C) 2007 Christian Brugger, Stefan Hacker
\r
7 # This program is free software; you can redistribute it and/or modify
\r
8 # it under the terms of the GNU General Public License as published by
\r
9 # the Free Software Foundation; either version 2 of the License, or
\r
10 # (at your option) any later version.
\r
12 # This program is distributed in the hope that it will be useful,
\r
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
15 # GNU General Public License for more details.
\r
17 # You should have received a copy of the GNU General Public License along
\r
18 # with this program; if not, write to the Free Software Foundation, Inc.,
\r
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
22 from OpenGL.GL import *
\r
25 Optimize with psyco if possible, this gains us about 50% speed when
\r
26 creating our textures in trade for about 4MBytes of additional memory usage for
\r
27 psyco. If you don't like loosing the memory you have to turn the lines following
\r
28 "enable psyco" into a comment while uncommenting the line after "Disable psyco".
\r
30 #Try to enable psyco
\r
33 psyco_optimized = False
\r
40 class TextElement(object):
\r
42 A simple class for using system Fonts to display
\r
43 text in an OpenGL scene
\r
48 foreground = wx.BLACK,
\r
51 text (String) - Text
\r
52 font (wx.Font) - Font to draw with (None = System default)
\r
53 foreground (wx.Color) - Color of the text
\r
54 or (wx.Bitmap)- Bitmap to overlay the text with
\r
55 centered (bool) - Center the text
\r
57 Initializes the TextElement
\r
59 # save given variables
\r
61 self._lines = text.split('\n')
\r
63 self._foreground = foreground
\r
64 self._centered = centered
\r
66 # init own variables
\r
67 self._owner_cnt = 0 #refcounter
\r
68 self._texture = None #OpenGL texture ID
\r
69 self._text_size = None #x/y size tuple of the text
\r
70 self._texture_size= None #x/y Texture size tuple
\r
73 self.createTexture()
\r
76 #---Internal helpers
\r
78 def _getUpper2Base(self, value):
\r
80 Returns the lowest value with the power of
\r
81 2 greater than 'value' (2^n>value)
\r
84 while base2 < value:
\r
90 def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):
\r
92 position (wx.Point) - x/y Position to draw in scene
\r
93 scale (float) - Scale
\r
94 rotation (int) - Rotation in degree
\r
96 Draws the text to the scene
\r
98 #Enable necessary functions
\r
100 glEnable(GL_TEXTURE_2D)
\r
101 glEnable(GL_ALPHA_TEST) #Enable alpha test
\r
102 glAlphaFunc(GL_GREATER, 0)
\r
103 glEnable(GL_BLEND) #Enable blending
\r
104 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
\r
106 glBindTexture(GL_TEXTURE_2D, self._texture)
\r
108 ow, oh = self._text_size
\r
109 w , h = self._texture_size
\r
110 #Perform transformations
\r
112 glTranslated(position.x, position.y, 0)
\r
113 glRotate(-rotation, 0, 0, 1)
\r
114 glScaled(scale, scale, scale)
\r
116 glTranslate(-w/2, -oh/2, 0)
\r
119 glTexCoord2f(0,0); glVertex2f(0,0)
\r
120 glTexCoord2f(0,1); glVertex2f(0,h)
\r
121 glTexCoord2f(1,1); glVertex2f(w,h)
\r
122 glTexCoord2f(1,0); glVertex2f(w,0)
\r
127 glDisable(GL_BLEND)
\r
128 glDisable(GL_ALPHA_TEST)
\r
129 glDisable(GL_TEXTURE_2D)
\r
131 def createTexture(self):
\r
133 Creates a texture from the settings saved in TextElement, to be able to use normal
\r
134 system fonts conviently a wx.MemoryDC is used to draw on a wx.Bitmap. As wxwidgets
\r
135 device contexts don't support alpha at all it is necessary to apply a little hack
\r
136 to preserve antialiasing without sticking to a fixed background color:
\r
138 We draw the bmp in b/w mode so we can use its data as a alpha channel for a solid
\r
139 color bitmap which after GL_ALPHA_TEST and GL_BLEND will show a nicely antialiased
\r
140 text on any surface.
\r
142 To access the raw pixel data the bmp gets converted to a wx.Image. Now we just have
\r
143 to merge our foreground color with the alpha data we just created and push it all
\r
144 into a OpenGL texture and we are DONE *inhalesdelpy*
\r
146 DRAWBACK of the whole conversion thing is a really long time for creating the
\r
147 texture. If you see any optimizations that could save time PLEASE CREATE A PATCH!!!
\r
153 dc.SetFont(self._font)
\r
155 # Approximate extend to next power of 2 and create our bitmap
\r
156 # REMARK: You wouldn't believe how much fucking speed this little
\r
157 # sucker gains compared to sizes not of the power of 2. It's like
\r
158 # 500ms --> 0.5ms (on my ATI-GPU powered Notebook). On Sams nvidia
\r
159 # machine there don't seem to occur any losses...bad drivers?
\r
160 ow, oh = dc.GetMultiLineTextExtent(self._text)[:2]
\r
161 w, h = self._getUpper2Base(ow), self._getUpper2Base(oh)
\r
163 self._text_size = wx.Size(ow,oh)
\r
164 self._texture_size = wx.Size(w,h)
\r
165 bmp = wx.EmptyBitmap(w,h)
\r
168 #Draw in b/w mode to bmp so we can use it as alpha channel
\r
169 dc.SelectObject(bmp)
\r
170 dc.SetBackground(wx.BLACK_BRUSH)
\r
172 dc.SetTextForeground(wx.WHITE)
\r
174 centered = self.centered
\r
175 for line in self._lines:
\r
176 if not line: line = ' '
\r
177 tw, th = dc.GetTextExtent(line)
\r
179 x = int(round((w-tw)/2))
\r
180 dc.DrawText(line, x, y)
\r
184 dc.SelectObject(wx.NullBitmap)
\r
187 #Generate a correct RGBA data string from our bmp
\r
189 NOTE: You could also use wx.AlphaPixelData to access the pixel data
\r
190 in 'bmp' directly, but the iterator given by it is much slower than
\r
191 first converting to an image and using wx.Image.GetData().
\r
193 img = wx.ImageFromBitmap(bmp)
\r
194 alpha = img.GetData()
\r
196 if isinstance(self._foreground, wx.Color):
\r
198 If we have a static color...
\r
200 r,g,b = self._foreground.Get()
\r
201 color = "%c%c%c" % (chr(r), chr(g), chr(b))
\r
204 for i in xrange(0, len(alpha)-1, 3):
\r
205 data += color + alpha[i]
\r
207 elif isinstance(self._foreground, wx.Bitmap):
\r
209 If we have a bitmap...
\r
211 bg_img = wx.ImageFromBitmap(self._foreground)
\r
212 bg = bg_img.GetData()
\r
213 bg_width = self._foreground.GetWidth()
\r
214 bg_height = self._foreground.GetHeight()
\r
218 for y in xrange(0, h):
\r
219 for x in xrange(0, w):
\r
220 if (y > (bg_height-1)) or (x > (bg_width-1)):
\r
221 color = "%c%c%c" % (chr(0),chr(0),chr(0))
\r
223 pos = (x+y*bg_width) * 3
\r
224 color = bg[pos:pos+3]
\r
225 data += color + alpha[(x+y*w)*3]
\r
228 # now convert it to ogl texture
\r
229 self._texture = glGenTextures(1)
\r
230 glBindTexture(GL_TEXTURE_2D, self._texture)
\r
231 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
\r
232 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
\r
234 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)
\r
235 glPixelStorei(GL_UNPACK_ALIGNMENT, 2)
\r
236 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
\r
238 def deleteTexture(self):
\r
240 Deletes the OpenGL texture object
\r
243 if glIsTexture(self._texture):
\r
244 glDeleteTextures(self._texture)
\r
246 self._texture = None
\r
252 self._owner_cnt += 1
\r
258 self._owner_cnt -= 1
\r
264 return self._owner_cnt
\r
270 self.deleteTexture()
\r
272 #---Getters/Setters
\r
274 def getText(self): return self._text
\r
275 def getFont(self): return self._font
\r
276 def getForeground(self): return self._foreground
\r
277 def getCentered(self): return self._centered
\r
278 def getTexture(self): return self._texture
\r
279 def getTexture_size(self): return self._texture_size
\r
281 def getOwner_cnt(self): return self._owner_cnt
\r
282 def setOwner_cnt(self, value):
\r
283 self._owner_cnt = value
\r
287 text = property(getText, None, None, "Text of the object")
\r
288 font = property(getFont, None, None, "Font of the object")
\r
289 foreground = property(getForeground, None, None, "Color of the text")
\r
290 centered = property(getCentered, None, None, "Is text centered")
\r
291 owner_cnt = property(getOwner_cnt, setOwner_cnt, None, "Owner count")
\r
292 texture = property(getTexture, None, None, "Used texture")
\r
293 texture_size = property(getTexture_size, None, None, "Size of the used texture")
\r
296 class Text(object):
\r
298 A simple class for using System Fonts to display text in
\r
299 an OpenGL scene. The Text adds a global Cache of already
\r
300 created text elements to TextElement's base functionality
\r
301 so you can save some memory and increase speed
\r
303 _texts = [] #Global cache for TextElements
\r
309 foreground = wx.BLACK,
\r
313 text (string) - displayed text
\r
314 font (wx.Font) - if None, system default font will be used with font_size
\r
315 font_size (int) - font size in points
\r
316 foreground (wx.Color) - Color of the text
\r
317 or (wx.Bitmap) - Bitmap to overlay the text with
\r
318 centered (bool) - should the text drawn centered towards position?
\r
320 Initializes the text object
\r
322 #Init/save variables
\r
323 self._aloc_text = None
\r
325 self._font_size = font_size
\r
326 self._foreground= foreground
\r
327 self._centered = centered
\r
329 #Check if we are offered a font
\r
331 #if not use the system default
\r
332 self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
\r
337 if bold: self._font.SetWeight(wx.FONTWEIGHT_BOLD)
\r
339 #Bind us to our texture
\r
342 #---Internal helpers
\r
344 def _initText(self):
\r
346 Initializes/Reinitializes the Text object by binding it
\r
347 to a TextElement suitable for its current settings
\r
349 #Check if we already bound to a texture
\r
350 if self._aloc_text:
\r
352 self._aloc_text.release()
\r
353 if not self._aloc_text.isBound():
\r
354 self._texts.remove(self._aloc_text)
\r
355 self._aloc_text = None
\r
358 self._font.SetPointSize(self._font_size)
\r
360 #Search for existing element in our global buffer
\r
361 for element in self._texts:
\r
362 if element.text == self._text and\
\r
363 element.font == self._font and\
\r
364 element.foreground == self._foreground and\
\r
365 element.centered == self._centered:
\r
366 # We already exist in global buffer ;-)
\r
368 self._aloc_text = element
\r
371 if not self._aloc_text:
\r
372 # We are not in the global buffer, let's create ourselves
\r
373 aloc_text = self._aloc_text = TextElement(self._text,
\r
378 self._texts.append(aloc_text)
\r
384 aloc_text = self._aloc_text
\r
385 aloc_text.release()
\r
386 if not aloc_text.isBound():
\r
387 self._texts.remove(aloc_text)
\r
391 def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):
\r
393 position (wx.Point) - x/y Position to draw in scene
\r
394 scale (float) - Scale
\r
395 rotation (int) - Rotation in degree
\r
397 Draws the text to the scene
\r
400 self._aloc_text.draw_text(position, scale, rotation)
\r
404 def getText(self): return self._text
\r
405 def setText(self, value, reinit = True):
\r
407 value (bool) - New Text
\r
408 reinit (bool) - Create a new texture
\r
416 def getFont(self): return self._font
\r
417 def setFont(self, value, reinit = True):
\r
419 value (bool) - New Font
\r
420 reinit (bool) - Create a new texture
\r
428 def getFont_size(self): return self._font_size
\r
429 def setFont_size(self, value, reinit = True):
\r
431 value (bool) - New font size
\r
432 reinit (bool) - Create a new texture
\r
434 Sets a new font size
\r
436 self._font_size = value
\r
440 def getForeground(self): return self._foreground
\r
441 def setForeground(self, value, reinit = True):
\r
443 value (bool) - New centered value
\r
444 reinit (bool) - Create a new texture
\r
446 Sets a new value for 'centered'
\r
448 self._foreground = value
\r
452 def getCentered(self): return self._centered
\r
453 def setCentered(self, value, reinit = True):
\r
455 value (bool) - New centered value
\r
456 reinit (bool) - Create a new texture
\r
458 Sets a new value for 'centered'
\r
460 self._centered = value
\r
464 def get_size(self):
\r
466 Returns a text size tuple
\r
468 return self._aloc_text._text_size
\r
470 def getTexture_size(self):
\r
472 Returns a texture size tuple
\r
474 return self._aloc_text.texture_size
\r
476 def getTextElement(self):
\r
478 Returns the text element bound to the Text class
\r
480 return self._aloc_text
\r
482 def getTexture(self):
\r
484 Returns the texture of the bound TextElement
\r
486 return self._aloc_text.texture
\r
491 text = property(getText, setText, None, "Text of the object")
\r
492 font = property(getFont, setFont, None, "Font of the object")
\r
493 font_size = property(getFont_size, setFont_size, None, "Font size")
\r
494 foreground = property(getForeground, setForeground, None, "Color/Overlay bitmap of the text")
\r
495 centered = property(getCentered, setCentered, None, "Display the text centered")
\r
496 texture_size = property(getTexture_size, None, None, "Size of the used texture")
\r
497 texture = property(getTexture, None, None, "Texture of bound TextElement")
\r
498 text_element = property(getTextElement,None , None, "TextElement bound to this class")
\r
500 #Optimize critical functions
\r
501 if psyco and not psyco_optimized:
\r
502 psyco.bind(TextElement.createTexture)
\r
503 psyco_optimized = True
\r