source: trunk/gltext.py @ 1448

Last change on this file since 1448 was 1448, checked in by toby, 9 years ago

quick wx2.9+ fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 16.9 KB
Line 
1'''
2*gltext: draw OpenGL text*
3==========================
4
5Routines that render text on OpenGL without use of GLUT.
6
7Code written by Christian Brugger & Stefan Hacker and
8distributed under GNU General Public License.
9'''
10
11#!/usr/bin/env python
12# -*- coding: utf-8
13#
14#    Provides some text display functions for wx + ogl
15#    Copyright (C) 2007 Christian Brugger, Stefan Hacker
16#
17#    This program is free software; you can redistribute it and/or modify
18#    it under the terms of the GNU General Public License as published by
19#    the Free Software Foundation; either version 2 of the License, or
20#    (at your option) any later version.
21#
22#    This program is distributed in the hope that it will be useful,
23#    but WITHOUT ANY WARRANTY; without even the implied warranty of
24#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25#    GNU General Public License for more details.
26#
27#    You should have received a copy of the GNU General Public License along
28#    with this program; if not, write to the Free Software Foundation, Inc.,
29#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30
31import wx
32from OpenGL.GL import *
33
34"""
35Optimize with psyco if possible, this gains us about 50% speed when
36creating our textures in trade for about 4MBytes of additional memory usage for
37psyco. If you don't like loosing the memory you have to turn the lines following
38"enable psyco" into a comment while uncommenting the line after "Disable psyco".
39"""
40#Try to enable psyco
41#try:
42#    import psyco
43#    psyco_optimized = False
44#except ImportError:
45#    psyco = None
46   
47#Disable psyco
48psyco = None
49         
50class TextElement(object):
51    """
52    A simple class for using system Fonts to display
53    text in an OpenGL scene
54    """
55    def __init__(self,
56                 text = '',
57                 font = None,
58                 foreground = wx.WHITE,
59                 centered = False):
60        """
61        text (String)         - Text
62        font (wx.Font)        - Font to draw with (None = System default)
63        foreground (wx.Colour)- Color of the text
64                or (wx.Bitmap)- Bitmap to overlay the text with
65        centered (bool)       - Center the text
66       
67        Initializes the TextElement
68        """
69        # save given variables
70        self._text        = text
71        self._lines       = text.split('\n')
72        self._font        = font
73        self._foreground  = foreground
74        self._centered    = centered
75       
76        # init own variables
77        self._owner_cnt   = 0        #refcounter
78        self._texture     = None     #OpenGL texture ID
79        self._text_size   = None     #x/y size tuple of the text
80        self._texture_size= None     #x/y Texture size tuple
81       
82        # create Texture
83        self.createTexture()
84       
85
86    #---Internal helpers
87   
88    def _getUpper2Base(self, value):
89        """
90        Returns the lowest value with the power of
91        2 greater than 'value' (2^n>value)
92        """
93        base2 = 1
94        while base2 < value:
95            base2 *= 2
96        return base2
97       
98    #---Functions
99   
100    def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):
101        """
102        position (wx.Point)    - x/y Position to draw in scene
103        scale    (float)       - Scale
104        rotation (int)         - Rotation in degree
105       
106        Draws the text to the scene
107        """
108        #Enable necessary functions
109        glColor(1,1,1,1)
110        glEnable(GL_TEXTURE_2D)     
111        glEnable(GL_ALPHA_TEST)       #Enable alpha test
112        glAlphaFunc(GL_GREATER, 0)
113        glEnable(GL_BLEND)            #Enable blending
114        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
115        #Bind texture
116        glBindTexture(GL_TEXTURE_2D, self._texture)
117       
118        ow, oh = self._text_size
119        w , h  = self._texture_size
120        #Perform transformations
121        glPushMatrix()
122        glTranslated(position.x, position.y, 0)
123        glRotate(-rotation, 0, 0, 1)
124        glScaled(scale, scale, scale)
125        if self._centered:
126            glTranslate(-w/2, -oh/2, 0)
127        #Draw vertices
128        glBegin(GL_QUADS)
129        glTexCoord2f(0,0); glVertex2f(0,0)
130        glTexCoord2f(0,1); glVertex2f(0,h)
131        glTexCoord2f(1,1); glVertex2f(w,h)
132        glTexCoord2f(1,0); glVertex2f(w,0)
133        glEnd()
134        glPopMatrix()
135       
136        #Disable features
137        glDisable(GL_BLEND)
138        glDisable(GL_ALPHA_TEST)
139        glDisable(GL_TEXTURE_2D)
140       
141    def createTexture(self):
142        """
143        Creates a texture from the settings saved in TextElement, to be able to use normal
144        system fonts conviently a wx.MemoryDC is used to draw on a wx.Bitmap. As wxwidgets
145        device contexts don't support alpha at all it is necessary to apply a little hack
146        to preserve antialiasing without sticking to a fixed background color:
147       
148        We draw the bmp in b/w mode so we can use its data as a alpha channel for a solid
149        color bitmap which after GL_ALPHA_TEST and GL_BLEND will show a nicely antialiased
150        text on any surface.
151       
152        To access the raw pixel data the bmp gets converted to a wx.Image. Now we just have
153        to merge our foreground color with the alpha data we just created and push it all
154        into a OpenGL texture and we are DONE *inhalesdelpy*
155       
156        DRAWBACK of the whole conversion thing is a really long time for creating the
157        texture. If you see any optimizations that could save time PLEASE CREATE A PATCH!!!
158        """
159        # get a memory dc and assign a temporary bitmap
160        dc = wx.MemoryDC()
161        dc.SelectObject(wx.EmptyBitmap(100, 100))
162       
163        # set our font
164        dc.SetFont(self._font)
165       
166        # Approximate extend to next power of 2 and create our bitmap
167        # REMARK: You wouldn't believe how much fucking speed this little
168        #         sucker gains compared to sizes not of the power of 2. It's like
169        #         500ms --> 0.5ms (on my ATI-GPU powered Notebook). On Sams nvidia
170        #         machine there don't seem to occur any losses...bad drivers?
171        ow, oh = dc.GetMultiLineTextExtent(self._text)[:2]
172        w, h = self._getUpper2Base(ow), self._getUpper2Base(oh)
173       
174        self._text_size = wx.Size(ow,oh)
175        self._texture_size = wx.Size(w,h)
176        bmp = wx.EmptyBitmap(w,h)
177       
178       
179        #Draw in b/w mode to bmp so we can use it as alpha channel
180        dc.SelectObject(bmp)
181        dc.SetBackground(wx.BLACK_BRUSH)
182        dc.Clear()
183        dc.SetTextForeground(wx.WHITE)
184        x,y = 0,0
185        centered = self.centered
186        for line in self._lines:
187            if not line: line = ' '
188            tw, th = dc.GetTextExtent(line)
189            if centered:
190                x = int(round((w-tw)/2))
191            dc.DrawText(line, x, y)
192            x = 0
193            y += th
194        #Release the dc
195        dc.SelectObject(wx.NullBitmap)
196        del dc
197
198        #Generate a correct RGBA data string from our bmp
199        """
200        NOTE: You could also use wx.AlphaPixelData to access the pixel data
201        in 'bmp' directly, but the iterator given by it is much slower than
202        first converting to an image and using wx.Image.GetData().
203        """
204        img   = wx.ImageFromBitmap(bmp)
205        alpha = img.GetData()
206       
207        if isinstance(self._foreground, wx.Colour): 
208            """
209            If we have a static color... 
210            """   
211            r,g,b = self._foreground.Get()
212            color = "%c%c%c" % (chr(r), chr(g), chr(b))
213           
214            data = ''
215            for i in xrange(0, len(alpha)-1, 3):
216                data += color + alpha[i]
217       
218        elif isinstance(self._foreground, wx.Bitmap):
219            """
220            If we have a bitmap...
221            """
222            bg_img    = wx.ImageFromBitmap(self._foreground)
223            bg        = bg_img.GetData()
224            bg_width  = self._foreground.GetWidth()
225            bg_height = self._foreground.GetHeight()
226           
227            data = ''
228
229            for y in xrange(0, h):
230                for x in xrange(0, w):
231                    if (y > (bg_height-1)) or (x > (bg_width-1)):                       
232                        color = "%c%c%c" % (chr(0),chr(0),chr(0))
233                    else:
234                        pos = (x+y*bg_width) * 3
235                        color = bg[pos:pos+3]
236                    data += color + alpha[(x+y*w)*3]
237
238
239        # now convert it to ogl texture
240        self._texture = glGenTextures(1)
241        glBindTexture(GL_TEXTURE_2D, self._texture)
242        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
243        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
244       
245        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)
246        glPixelStorei(GL_UNPACK_ALIGNMENT, 2)
247        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
248   
249    def deleteTexture(self):
250        """
251        Deletes the OpenGL texture object
252        """
253        if self._texture:
254            if glIsTexture(self._texture):
255                glDeleteTextures(self._texture)
256            else:
257                self._texture = None
258
259    def bind(self):
260        """
261        Increase refcount
262        """
263        self._owner_cnt += 1
264   
265    def release(self):
266        """
267        Decrease refcount
268        """
269        self._owner_cnt -= 1
270       
271    def isBound(self):
272        """
273        Return refcount
274        """
275        return self._owner_cnt
276       
277    def __del__(self):
278        """
279        Destructor
280        """
281        self.deleteTexture()
282
283    #---Getters/Setters
284   
285    def getText(self): return self._text
286    def getFont(self): return self._font
287    def getForeground(self): return self._foreground
288    def getCentered(self): return self._centered
289    def getTexture(self): return self._texture
290    def getTexture_size(self): return self._texture_size
291
292    def getOwner_cnt(self): return self._owner_cnt
293    def setOwner_cnt(self, value):
294        self._owner_cnt = value
295       
296    #---Properties
297   
298    text         = property(getText, None, None, "Text of the object")
299    font         = property(getFont, None, None, "Font of the object")
300    foreground   = property(getForeground, None, None, "Color of the text")
301    centered     = property(getCentered, None, None, "Is text centered")
302    owner_cnt    = property(getOwner_cnt, setOwner_cnt, None, "Owner count")
303    texture      = property(getTexture, None, None, "Used texture")
304    texture_size = property(getTexture_size, None, None, "Size of the used texture")       
305               
306
307class Text(object):
308    """
309    A simple class for using System Fonts to display text in
310    an OpenGL scene. The Text adds a global Cache of already
311    created text elements to TextElement's base functionality
312    so you can save some memory and increase speed
313    """
314    _texts         = []    #Global cache for TextElements
315   
316    def __init__(self,
317                 text = 'Text',
318                 font = None,
319                 font_size = 8,
320                 foreground = wx.WHITE,
321                 centered = False):
322        """
323            text (string)           - displayed text
324            font (wx.Font)          - if None, system default font will be used with font_size
325            font_size (int)         - font size in points
326            foreground (wx.Colour)   - Color of the text
327                    or (wx.Bitmap)  - Bitmap to overlay the text with
328            centered (bool)         - should the text drawn centered towards position?
329           
330            Initializes the text object
331        """
332        #Init/save variables
333        self._aloc_text = None
334        self._text      = text
335        self._font_size = font_size
336        self._foreground= foreground
337        self._centered  = centered
338       
339        #Check if we are offered a font
340        if not font:
341            #if not use the system default
342            self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
343        else: 
344            #save it
345            self._font = font
346           
347        #Bind us to our texture
348        self._initText()
349
350    #---Internal helpers
351
352    def _initText(self):
353        """
354        Initializes/Reinitializes the Text object by binding it
355        to a TextElement suitable for its current settings
356        """
357        #Check if we already bound to a texture
358        if self._aloc_text:
359            #if so release it
360            self._aloc_text.release()
361            if not self._aloc_text.isBound():
362                self._texts.remove(self._aloc_text)
363            self._aloc_text = None
364           
365        #Adjust our font
366        self._font.SetPointSize(self._font_size)
367       
368        #Search for existing element in our global buffer
369        for element in self._texts:
370            if element.text == self._text and\
371              element.font == self._font and\
372              element.foreground == self._foreground and\
373              element.centered == self._centered:
374                # We already exist in global buffer ;-)
375                element.bind()
376                self._aloc_text = element
377                break
378       
379        if not self._aloc_text:
380            # We are not in the global buffer, let's create ourselves
381            aloc_text = self._aloc_text = TextElement(self._text,
382                                                       self._font,
383                                                       self._foreground,
384                                                       self._centered)
385            aloc_text.bind()
386            self._texts.append(aloc_text)
387   
388    def __del__(self):
389        """
390        Destructor
391        """
392        aloc_text = self._aloc_text
393        aloc_text.release()
394        if not aloc_text.isBound():
395            self._texts.remove(aloc_text)
396   
397    #---Functions
398       
399    def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):
400        """
401        position (wx.Point)    - x/y Position to draw in scene
402        scale    (float)       - Scale
403        rotation (int)         - Rotation in degree
404       
405        Draws the text to the scene
406        """
407       
408        self._aloc_text.draw_text(position, scale, rotation)
409
410    #---Setter/Getter
411   
412    def getText(self): return self._text
413    def setText(self, value, reinit = True):
414        """
415        value (bool)    - New Text
416        reinit (bool)   - Create a new texture
417       
418        Sets a new text
419        """
420        self._text = value
421        if reinit:
422            self._initText()
423
424    def getFont(self): return self._font
425    def setFont(self, value, reinit = True):
426        """
427        value (bool)    - New Font
428        reinit (bool)   - Create a new texture
429       
430        Sets a new font
431        """
432        self._font = value
433        if reinit:
434            self._initText()
435
436    def getFont_size(self): return self._font_size
437    def setFont_size(self, value, reinit = True):
438        """
439        value (bool)    - New font size
440        reinit (bool)   - Create a new texture
441       
442        Sets a new font size
443        """
444        self._font_size = value
445        if reinit:
446            self._initText()
447
448    def getForeground(self): return self._foreground
449    def setForeground(self, value, reinit = True):
450        """
451        value (bool)    - New centered value
452        reinit (bool)   - Create a new texture
453       
454        Sets a new value for 'centered'
455        """
456        self._foreground = value
457        if reinit:
458            self._initText()
459
460    def getCentered(self): return self._centered
461    def setCentered(self, value, reinit = True):
462        """
463        value (bool)    - New centered value
464        reinit (bool)   - Create a new texture
465       
466        Sets a new value for 'centered'
467        """
468        self._centered = value
469        if reinit:
470            self._initText()
471   
472    def getTexture_size(self):
473        """
474        Returns a texture size tuple
475        """
476        return self._aloc_text.texture_size
477   
478    def getTextElement(self):
479        """
480        Returns the text element bound to the Text class
481        """
482        return self._aloc_text
483   
484    def getTexture(self):
485        """
486        Returns the texture of the bound TextElement
487        """
488        return self._aloc_text.texture
489
490   
491    #---Properties
492   
493    text         = property(getText, setText, None, "Text of the object")
494    font         = property(getFont, setFont, None, "Font of the object")
495    font_size    = property(getFont_size, setFont_size, None, "Font size")
496    foreground   = property(getForeground, setForeground, None, "Color/Overlay bitmap of the text")
497    centered     = property(getCentered, setCentered, None, "Display the text centered")
498    texture_size = property(getTexture_size, None, None, "Size of the used texture")
499    texture      = property(getTexture, None, None, "Texture of bound TextElement")
500    text_element = property(getTextElement,None , None, "TextElement bound to this class")
501
502#Optimize critical functions
503if psyco and not psyco_optimized:
504    psyco.bind(TextElement.createTexture)
505    psyco_optimized = True
Note: See TracBrowser for help on using the repository browser.