Imported Upstream version 3.2.2
[debian/gnuradio] / gr-wxgui / src / python / plotter / gltext.py
1 #!/usr/bin/env python\r
2 # -*- coding: utf-8\r
3 #\r
4 #    Provides some text display functions for wx + ogl\r
5 #    Copyright (C) 2007 Christian Brugger, Stefan Hacker\r
6 #\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
11 #\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
16 #\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
20 \r
21 import wx\r
22 from OpenGL.GL import *\r
23 \r
24 """\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
29 """\r
30 #Try to enable psyco\r
31 try:\r
32     import psyco\r
33     psyco_optimized = False\r
34 except ImportError:\r
35     psyco = None\r
36     \r
37 #Disable psyco\r
38 #psyco = None\r
39           \r
40 class TextElement(object):\r
41     """\r
42     A simple class for using system Fonts to display\r
43     text in an OpenGL scene\r
44     """\r
45     def __init__(self,\r
46                  text = '',\r
47                  font = None,\r
48                  foreground = wx.BLACK,\r
49                  centered = False):\r
50         """\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
56         \r
57         Initializes the TextElement\r
58         """\r
59         # save given variables\r
60         self._text        = text\r
61         self._lines       = text.split('\n')\r
62         self._font        = font\r
63         self._foreground  = foreground\r
64         self._centered    = centered\r
65         \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
71         \r
72         # create Texture\r
73         self.createTexture()\r
74         \r
75 \r
76     #---Internal helpers\r
77     \r
78     def _getUpper2Base(self, value):\r
79         """\r
80         Returns the lowest value with the power of\r
81         2 greater than 'value' (2^n>value)\r
82         """\r
83         base2 = 1\r
84         while base2 < value:\r
85             base2 *= 2\r
86         return base2\r
87         \r
88     #---Functions\r
89     \r
90     def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):\r
91         """\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
95         \r
96         Draws the text to the scene\r
97         """\r
98         #Enable necessary functions\r
99         glColor(1,1,1,1)\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
105         #Bind texture\r
106         glBindTexture(GL_TEXTURE_2D, self._texture)\r
107         \r
108         ow, oh = self._text_size\r
109         w , h  = self._texture_size\r
110         #Perform transformations\r
111         glPushMatrix()\r
112         glTranslated(position.x, position.y, 0)\r
113         glRotate(-rotation, 0, 0, 1)\r
114         glScaled(scale, scale, scale)\r
115         if self._centered:\r
116             glTranslate(-w/2, -oh/2, 0)\r
117         #Draw vertices\r
118         glBegin(GL_QUADS)\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
123         glEnd()\r
124         glPopMatrix()\r
125         \r
126         #Disable features\r
127         glDisable(GL_BLEND)\r
128         glDisable(GL_ALPHA_TEST)\r
129         glDisable(GL_TEXTURE_2D)\r
130         \r
131     def createTexture(self):\r
132         """\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
137         \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
141         \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
145         \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
148         """\r
149         # get a memory dc\r
150         dc = wx.MemoryDC()\r
151         \r
152         # set our font\r
153         dc.SetFont(self._font)\r
154         \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
162         \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
166         \r
167         \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
171         dc.Clear()\r
172         dc.SetTextForeground(wx.WHITE)\r
173         x,y = 0,0\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
178             if centered:\r
179                 x = int(round((w-tw)/2))\r
180             dc.DrawText(line, x, y)\r
181             x = 0\r
182             y += th\r
183         #Release the dc\r
184         dc.SelectObject(wx.NullBitmap)\r
185         del dc\r
186 \r
187         #Generate a correct RGBA data string from our bmp \r
188         """\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
192         """\r
193         img   = wx.ImageFromBitmap(bmp)\r
194         alpha = img.GetData()\r
195         \r
196         if isinstance(self._foreground, wx.Color):  \r
197             """\r
198             If we have a static color...  \r
199             """    \r
200             r,g,b = self._foreground.Get()\r
201             color = "%c%c%c" % (chr(r), chr(g), chr(b))\r
202             \r
203             data = ''\r
204             for i in xrange(0, len(alpha)-1, 3):\r
205                 data += color + alpha[i]\r
206         \r
207         elif isinstance(self._foreground, wx.Bitmap):\r
208             """\r
209             If we have a bitmap...\r
210             """\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
215             \r
216             data = ''\r
217 \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
222                     else:\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
226 \r
227 \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
233         \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
237     \r
238     def deleteTexture(self):\r
239         """\r
240         Deletes the OpenGL texture object\r
241         """\r
242         if self._texture:\r
243             if glIsTexture(self._texture):\r
244                 glDeleteTextures(self._texture)\r
245             else:\r
246                 self._texture = None\r
247 \r
248     def bind(self):\r
249         """\r
250         Increase refcount\r
251         """\r
252         self._owner_cnt += 1\r
253     \r
254     def release(self):\r
255         """\r
256         Decrease refcount\r
257         """\r
258         self._owner_cnt -= 1\r
259         \r
260     def isBound(self):\r
261         """\r
262         Return refcount\r
263         """\r
264         return self._owner_cnt\r
265         \r
266     def __del__(self):\r
267         """\r
268         Destructor\r
269         """\r
270         self.deleteTexture()\r
271 \r
272     #---Getters/Setters\r
273     \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
280 \r
281     def getOwner_cnt(self): return self._owner_cnt\r
282     def setOwner_cnt(self, value):\r
283         self._owner_cnt = value\r
284         \r
285     #---Properties\r
286     \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
294                \r
295 \r
296 class Text(object):\r
297     """\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
302     """\r
303     _texts         = []    #Global cache for TextElements\r
304     \r
305     def __init__(self,\r
306                  text = 'Text',\r
307                  font = None,\r
308                  font_size = 8,\r
309                  foreground = wx.BLACK,\r
310                  centered = False,\r
311                  bold = False):\r
312         """\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
319             \r
320             Initializes the text object\r
321         """\r
322         #Init/save variables\r
323         self._aloc_text = None\r
324         self._text      = text\r
325         self._font_size = font_size\r
326         self._foreground= foreground\r
327         self._centered  = centered\r
328         \r
329         #Check if we are offered a font\r
330         if not font:\r
331             #if not use the system default\r
332             self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)\r
333         else: \r
334             #save it\r
335             self._font = font\r
336         \r
337         if bold: self._font.SetWeight(wx.FONTWEIGHT_BOLD)\r
338         \r
339         #Bind us to our texture\r
340         self._initText()\r
341 \r
342     #---Internal helpers\r
343 \r
344     def _initText(self):\r
345         """\r
346         Initializes/Reinitializes the Text object by binding it\r
347         to a TextElement suitable for its current settings\r
348         """\r
349         #Check if we already bound to a texture\r
350         if self._aloc_text:\r
351             #if so release it\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
356             \r
357         #Adjust our font\r
358         self._font.SetPointSize(self._font_size)\r
359         \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
367                 element.bind()\r
368                 self._aloc_text = element\r
369                 break\r
370         \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
374                                                        self._font,\r
375                                                        self._foreground,\r
376                                                        self._centered)\r
377             aloc_text.bind()\r
378             self._texts.append(aloc_text)\r
379     \r
380     def __del__(self):\r
381         """\r
382         Destructor\r
383         """\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
388     \r
389     #---Functions\r
390         \r
391     def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):\r
392         """\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
396         \r
397         Draws the text to the scene\r
398         """\r
399         \r
400         self._aloc_text.draw_text(position, scale, rotation)\r
401 \r
402     #---Setter/Getter\r
403     \r
404     def getText(self): return self._text\r
405     def setText(self, value, reinit = True):\r
406         """\r
407         value (bool)    - New Text\r
408         reinit (bool)   - Create a new texture\r
409         \r
410         Sets a new text\r
411         """\r
412         self._text = value\r
413         if reinit:\r
414             self._initText()\r
415 \r
416     def getFont(self): return self._font\r
417     def setFont(self, value, reinit = True):\r
418         """\r
419         value (bool)    - New Font\r
420         reinit (bool)   - Create a new texture\r
421         \r
422         Sets a new font\r
423         """\r
424         self._font = value\r
425         if reinit:\r
426             self._initText()\r
427 \r
428     def getFont_size(self): return self._font_size\r
429     def setFont_size(self, value, reinit = True):\r
430         """\r
431         value (bool)    - New font size\r
432         reinit (bool)   - Create a new texture\r
433         \r
434         Sets a new font size\r
435         """\r
436         self._font_size = value\r
437         if reinit:\r
438             self._initText()\r
439 \r
440     def getForeground(self): return self._foreground\r
441     def setForeground(self, value, reinit = True):\r
442         """\r
443         value (bool)    - New centered value\r
444         reinit (bool)   - Create a new texture\r
445         \r
446         Sets a new value for 'centered'\r
447         """\r
448         self._foreground = value\r
449         if reinit:\r
450             self._initText()\r
451 \r
452     def getCentered(self): return self._centered\r
453     def setCentered(self, value, reinit = True):\r
454         """\r
455         value (bool)    - New centered value\r
456         reinit (bool)   - Create a new texture\r
457         \r
458         Sets a new value for 'centered'\r
459         """\r
460         self._centered = value\r
461         if reinit:\r
462             self._initText()\r
463     \r
464     def get_size(self):\r
465         """\r
466         Returns a text size tuple\r
467         """\r
468         return self._aloc_text._text_size\r
469     \r
470     def getTexture_size(self):\r
471         """\r
472         Returns a texture size tuple\r
473         """\r
474         return self._aloc_text.texture_size\r
475     \r
476     def getTextElement(self):\r
477         """\r
478         Returns the text element bound to the Text class\r
479         """\r
480         return self._aloc_text\r
481     \r
482     def getTexture(self):\r
483         """\r
484         Returns the texture of the bound TextElement\r
485         """\r
486         return self._aloc_text.texture\r
487 \r
488     \r
489     #---Properties\r
490     \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
499 \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