source: trunk/gltext.py

Last change on this file was 4766, checked in by vondreele, 8 months ago

modify atom labeling routine to allow specified offset as wx.RealPoint? - now only used for MapPeaks? plotting

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