source: trunk/gltext.py @ 2826

Last change on this file since 2826 was 2591, checked in by vondreele, 8 years ago

replace openGl import method in gltext.py

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