source: trunk/gltext.py @ 987

Last change on this file since 987 was 987, checked in by vondreele, 10 years ago

GLUT eliminated! Now use gltext for all atom labelling

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