Imported Upstream version 3.2.2
[debian/gnuradio] / gr-wxgui / src / python / plotter / gltext.py
diff --git a/gr-wxgui/src/python/plotter/gltext.py b/gr-wxgui/src/python/plotter/gltext.py
new file mode 100644 (file)
index 0000000..67f62ca
--- /dev/null
@@ -0,0 +1,503 @@
+#!/usr/bin/env python\r
+# -*- coding: utf-8\r
+#\r
+#    Provides some text display functions for wx + ogl\r
+#    Copyright (C) 2007 Christian Brugger, Stefan Hacker\r
+#\r
+#    This program is free software; you can redistribute it and/or modify\r
+#    it under the terms of the GNU General Public License as published by\r
+#    the Free Software Foundation; either version 2 of the License, or\r
+#    (at your option) any later version.\r
+#\r
+#    This program is distributed in the hope that it will be useful,\r
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+#    GNU General Public License for more details.\r
+#\r
+#    You should have received a copy of the GNU General Public License along\r
+#    with this program; if not, write to the Free Software Foundation, Inc.,\r
+#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
+\r
+import wx\r
+from OpenGL.GL import *\r
+\r
+"""\r
+Optimize with psyco if possible, this gains us about 50% speed when\r
+creating our textures in trade for about 4MBytes of additional memory usage for\r
+psyco. If you don't like loosing the memory you have to turn the lines following\r
+"enable psyco" into a comment while uncommenting the line after "Disable psyco".\r
+"""\r
+#Try to enable psyco\r
+try:\r
+    import psyco\r
+    psyco_optimized = False\r
+except ImportError:\r
+    psyco = None\r
+    \r
+#Disable psyco\r
+#psyco = None\r
+          \r
+class TextElement(object):\r
+    """\r
+    A simple class for using system Fonts to display\r
+    text in an OpenGL scene\r
+    """\r
+    def __init__(self,\r
+                 text = '',\r
+                 font = None,\r
+                 foreground = wx.BLACK,\r
+                 centered = False):\r
+        """\r
+        text (String)         - Text\r
+        font (wx.Font)        - Font to draw with (None = System default)\r
+        foreground (wx.Color) - Color of the text\r
+                or (wx.Bitmap)- Bitmap to overlay the text with\r
+        centered (bool)       - Center the text\r
+        \r
+        Initializes the TextElement\r
+        """\r
+        # save given variables\r
+        self._text        = text\r
+        self._lines       = text.split('\n')\r
+        self._font        = font\r
+        self._foreground  = foreground\r
+        self._centered    = centered\r
+        \r
+        # init own variables\r
+        self._owner_cnt   = 0        #refcounter\r
+        self._texture     = None     #OpenGL texture ID\r
+        self._text_size   = None     #x/y size tuple of the text\r
+        self._texture_size= None     #x/y Texture size tuple\r
+        \r
+        # create Texture\r
+        self.createTexture()\r
+        \r
+\r
+    #---Internal helpers\r
+    \r
+    def _getUpper2Base(self, value):\r
+        """\r
+        Returns the lowest value with the power of\r
+        2 greater than 'value' (2^n>value)\r
+        """\r
+        base2 = 1\r
+        while base2 < value:\r
+            base2 *= 2\r
+        return base2\r
+        \r
+    #---Functions\r
+    \r
+    def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):\r
+        """\r
+        position (wx.Point)    - x/y Position to draw in scene\r
+        scale    (float)       - Scale\r
+        rotation (int)         - Rotation in degree\r
+        \r
+        Draws the text to the scene\r
+        """\r
+        #Enable necessary functions\r
+        glColor(1,1,1,1)\r
+        glEnable(GL_TEXTURE_2D)     \r
+        glEnable(GL_ALPHA_TEST)       #Enable alpha test\r
+        glAlphaFunc(GL_GREATER, 0)\r
+        glEnable(GL_BLEND)            #Enable blending\r
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)\r
+        #Bind texture\r
+        glBindTexture(GL_TEXTURE_2D, self._texture)\r
+        \r
+        ow, oh = self._text_size\r
+        w , h  = self._texture_size\r
+        #Perform transformations\r
+        glPushMatrix()\r
+        glTranslated(position.x, position.y, 0)\r
+        glRotate(-rotation, 0, 0, 1)\r
+        glScaled(scale, scale, scale)\r
+        if self._centered:\r
+            glTranslate(-w/2, -oh/2, 0)\r
+        #Draw vertices\r
+        glBegin(GL_QUADS)\r
+        glTexCoord2f(0,0); glVertex2f(0,0)\r
+        glTexCoord2f(0,1); glVertex2f(0,h)\r
+        glTexCoord2f(1,1); glVertex2f(w,h)\r
+        glTexCoord2f(1,0); glVertex2f(w,0)\r
+        glEnd()\r
+        glPopMatrix()\r
+        \r
+        #Disable features\r
+        glDisable(GL_BLEND)\r
+        glDisable(GL_ALPHA_TEST)\r
+        glDisable(GL_TEXTURE_2D)\r
+        \r
+    def createTexture(self):\r
+        """\r
+        Creates a texture from the settings saved in TextElement, to be able to use normal\r
+        system fonts conviently a wx.MemoryDC is used to draw on a wx.Bitmap. As wxwidgets \r
+        device contexts don't support alpha at all it is necessary to apply a little hack\r
+        to preserve antialiasing without sticking to a fixed background color:\r
+        \r
+        We draw the bmp in b/w mode so we can use its data as a alpha channel for a solid\r
+        color bitmap which after GL_ALPHA_TEST and GL_BLEND will show a nicely antialiased\r
+        text on any surface.\r
+        \r
+        To access the raw pixel data the bmp gets converted to a wx.Image. Now we just have\r
+        to merge our foreground color with the alpha data we just created and push it all\r
+        into a OpenGL texture and we are DONE *inhalesdelpy*\r
+        \r
+        DRAWBACK of the whole conversion thing is a really long time for creating the\r
+        texture. If you see any optimizations that could save time PLEASE CREATE A PATCH!!!\r
+        """\r
+        # get a memory dc\r
+        dc = wx.MemoryDC()\r
+        \r
+        # set our font\r
+        dc.SetFont(self._font)\r
+        \r
+        # Approximate extend to next power of 2 and create our bitmap\r
+        # REMARK: You wouldn't believe how much fucking speed this little\r
+        #         sucker gains compared to sizes not of the power of 2. It's like\r
+        #         500ms --> 0.5ms (on my ATI-GPU powered Notebook). On Sams nvidia\r
+        #         machine there don't seem to occur any losses...bad drivers?\r
+        ow, oh = dc.GetMultiLineTextExtent(self._text)[:2]\r
+        w, h = self._getUpper2Base(ow), self._getUpper2Base(oh)\r
+        \r
+        self._text_size = wx.Size(ow,oh)\r
+        self._texture_size = wx.Size(w,h)\r
+        bmp = wx.EmptyBitmap(w,h)\r
+        \r
+        \r
+        #Draw in b/w mode to bmp so we can use it as alpha channel\r
+        dc.SelectObject(bmp)\r
+        dc.SetBackground(wx.BLACK_BRUSH)\r
+        dc.Clear()\r
+        dc.SetTextForeground(wx.WHITE)\r
+        x,y = 0,0\r
+        centered = self.centered\r
+        for line in self._lines:\r
+            if not line: line = ' '\r
+            tw, th = dc.GetTextExtent(line)\r
+            if centered:\r
+                x = int(round((w-tw)/2))\r
+            dc.DrawText(line, x, y)\r
+            x = 0\r
+            y += th\r
+        #Release the dc\r
+        dc.SelectObject(wx.NullBitmap)\r
+        del dc\r
+\r
+        #Generate a correct RGBA data string from our bmp \r
+        """\r
+        NOTE: You could also use wx.AlphaPixelData to access the pixel data\r
+        in 'bmp' directly, but the iterator given by it is much slower than\r
+        first converting to an image and using wx.Image.GetData().\r
+        """\r
+        img   = wx.ImageFromBitmap(bmp)\r
+        alpha = img.GetData()\r
+        \r
+        if isinstance(self._foreground, wx.Color):  \r
+            """\r
+            If we have a static color...  \r
+            """    \r
+            r,g,b = self._foreground.Get()\r
+            color = "%c%c%c" % (chr(r), chr(g), chr(b))\r
+            \r
+            data = ''\r
+            for i in xrange(0, len(alpha)-1, 3):\r
+                data += color + alpha[i]\r
+        \r
+        elif isinstance(self._foreground, wx.Bitmap):\r
+            """\r
+            If we have a bitmap...\r
+            """\r
+            bg_img    = wx.ImageFromBitmap(self._foreground)\r
+            bg        = bg_img.GetData()\r
+            bg_width  = self._foreground.GetWidth()\r
+            bg_height = self._foreground.GetHeight()\r
+            \r
+            data = ''\r
+\r
+            for y in xrange(0, h):\r
+                for x in xrange(0, w):\r
+                    if (y > (bg_height-1)) or (x > (bg_width-1)):                       \r
+                        color = "%c%c%c" % (chr(0),chr(0),chr(0))\r
+                    else:\r
+                        pos = (x+y*bg_width) * 3\r
+                        color = bg[pos:pos+3]\r
+                    data += color + alpha[(x+y*w)*3]\r
+\r
+\r
+        # now convert it to ogl texture\r
+        self._texture = glGenTextures(1)\r
+        glBindTexture(GL_TEXTURE_2D, self._texture)\r
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)\r
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)\r
+        \r
+        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)\r
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 2)\r
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)\r
+    \r
+    def deleteTexture(self):\r
+        """\r
+        Deletes the OpenGL texture object\r
+        """\r
+        if self._texture:\r
+            if glIsTexture(self._texture):\r
+                glDeleteTextures(self._texture)\r
+            else:\r
+                self._texture = None\r
+\r
+    def bind(self):\r
+        """\r
+        Increase refcount\r
+        """\r
+        self._owner_cnt += 1\r
+    \r
+    def release(self):\r
+        """\r
+        Decrease refcount\r
+        """\r
+        self._owner_cnt -= 1\r
+        \r
+    def isBound(self):\r
+        """\r
+        Return refcount\r
+        """\r
+        return self._owner_cnt\r
+        \r
+    def __del__(self):\r
+        """\r
+        Destructor\r
+        """\r
+        self.deleteTexture()\r
+\r
+    #---Getters/Setters\r
+    \r
+    def getText(self): return self._text\r
+    def getFont(self): return self._font\r
+    def getForeground(self): return self._foreground\r
+    def getCentered(self): return self._centered\r
+    def getTexture(self): return self._texture\r
+    def getTexture_size(self): return self._texture_size\r
+\r
+    def getOwner_cnt(self): return self._owner_cnt\r
+    def setOwner_cnt(self, value):\r
+        self._owner_cnt = value\r
+        \r
+    #---Properties\r
+    \r
+    text         = property(getText, None, None, "Text of the object")\r
+    font         = property(getFont, None, None, "Font of the object")\r
+    foreground   = property(getForeground, None, None, "Color of the text")\r
+    centered     = property(getCentered, None, None, "Is text centered")\r
+    owner_cnt    = property(getOwner_cnt, setOwner_cnt, None, "Owner count")\r
+    texture      = property(getTexture, None, None, "Used texture")\r
+    texture_size = property(getTexture_size, None, None, "Size of the used texture")       \r
+               \r
+\r
+class Text(object):\r
+    """\r
+    A simple class for using System Fonts to display text in\r
+    an OpenGL scene. The Text adds a global Cache of already\r
+    created text elements to TextElement's base functionality\r
+    so you can save some memory and increase speed\r
+    """\r
+    _texts         = []    #Global cache for TextElements\r
+    \r
+    def __init__(self,\r
+                 text = 'Text',\r
+                 font = None,\r
+                 font_size = 8,\r
+                 foreground = wx.BLACK,\r
+                 centered = False,\r
+                 bold = False):\r
+        """\r
+            text (string)           - displayed text\r
+            font (wx.Font)          - if None, system default font will be used with font_size\r
+            font_size (int)         - font size in points\r
+            foreground (wx.Color)   - Color of the text\r
+                    or (wx.Bitmap)  - Bitmap to overlay the text with\r
+            centered (bool)         - should the text drawn centered towards position?\r
+            \r
+            Initializes the text object\r
+        """\r
+        #Init/save variables\r
+        self._aloc_text = None\r
+        self._text      = text\r
+        self._font_size = font_size\r
+        self._foreground= foreground\r
+        self._centered  = centered\r
+        \r
+        #Check if we are offered a font\r
+        if not font:\r
+            #if not use the system default\r
+            self._font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)\r
+        else: \r
+            #save it\r
+            self._font = font\r
+        \r
+        if bold: self._font.SetWeight(wx.FONTWEIGHT_BOLD)\r
+        \r
+        #Bind us to our texture\r
+        self._initText()\r
+\r
+    #---Internal helpers\r
+\r
+    def _initText(self):\r
+        """\r
+        Initializes/Reinitializes the Text object by binding it\r
+        to a TextElement suitable for its current settings\r
+        """\r
+        #Check if we already bound to a texture\r
+        if self._aloc_text:\r
+            #if so release it\r
+            self._aloc_text.release()\r
+            if not self._aloc_text.isBound():\r
+                self._texts.remove(self._aloc_text)\r
+            self._aloc_text = None\r
+            \r
+        #Adjust our font\r
+        self._font.SetPointSize(self._font_size)\r
+        \r
+        #Search for existing element in our global buffer\r
+        for element in self._texts:\r
+            if element.text == self._text and\\r
+              element.font == self._font and\\r
+              element.foreground == self._foreground and\\r
+              element.centered == self._centered:\r
+                # We already exist in global buffer ;-)\r
+                element.bind()\r
+                self._aloc_text = element\r
+                break\r
+        \r
+        if not self._aloc_text:\r
+            # We are not in the global buffer, let's create ourselves\r
+            aloc_text = self._aloc_text = TextElement(self._text,\r
+                                                       self._font,\r
+                                                       self._foreground,\r
+                                                       self._centered)\r
+            aloc_text.bind()\r
+            self._texts.append(aloc_text)\r
+    \r
+    def __del__(self):\r
+        """\r
+        Destructor\r
+        """\r
+        aloc_text = self._aloc_text\r
+        aloc_text.release()\r
+        if not aloc_text.isBound():\r
+            self._texts.remove(aloc_text)\r
+    \r
+    #---Functions\r
+        \r
+    def draw_text(self, position = wx.Point(0,0), scale = 1.0, rotation = 0):\r
+        """\r
+        position (wx.Point)    - x/y Position to draw in scene\r
+        scale    (float)       - Scale\r
+        rotation (int)         - Rotation in degree\r
+        \r
+        Draws the text to the scene\r
+        """\r
+        \r
+        self._aloc_text.draw_text(position, scale, rotation)\r
+\r
+    #---Setter/Getter\r
+    \r
+    def getText(self): return self._text\r
+    def setText(self, value, reinit = True):\r
+        """\r
+        value (bool)    - New Text\r
+        reinit (bool)   - Create a new texture\r
+        \r
+        Sets a new text\r
+        """\r
+        self._text = value\r
+        if reinit:\r
+            self._initText()\r
+\r
+    def getFont(self): return self._font\r
+    def setFont(self, value, reinit = True):\r
+        """\r
+        value (bool)    - New Font\r
+        reinit (bool)   - Create a new texture\r
+        \r
+        Sets a new font\r
+        """\r
+        self._font = value\r
+        if reinit:\r
+            self._initText()\r
+\r
+    def getFont_size(self): return self._font_size\r
+    def setFont_size(self, value, reinit = True):\r
+        """\r
+        value (bool)    - New font size\r
+        reinit (bool)   - Create a new texture\r
+        \r
+        Sets a new font size\r
+        """\r
+        self._font_size = value\r
+        if reinit:\r
+            self._initText()\r
+\r
+    def getForeground(self): return self._foreground\r
+    def setForeground(self, value, reinit = True):\r
+        """\r
+        value (bool)    - New centered value\r
+        reinit (bool)   - Create a new texture\r
+        \r
+        Sets a new value for 'centered'\r
+        """\r
+        self._foreground = value\r
+        if reinit:\r
+            self._initText()\r
+\r
+    def getCentered(self): return self._centered\r
+    def setCentered(self, value, reinit = True):\r
+        """\r
+        value (bool)    - New centered value\r
+        reinit (bool)   - Create a new texture\r
+        \r
+        Sets a new value for 'centered'\r
+        """\r
+        self._centered = value\r
+        if reinit:\r
+            self._initText()\r
+    \r
+    def get_size(self):\r
+        """\r
+        Returns a text size tuple\r
+        """\r
+        return self._aloc_text._text_size\r
+    \r
+    def getTexture_size(self):\r
+        """\r
+        Returns a texture size tuple\r
+        """\r
+        return self._aloc_text.texture_size\r
+    \r
+    def getTextElement(self):\r
+        """\r
+        Returns the text element bound to the Text class\r
+        """\r
+        return self._aloc_text\r
+    \r
+    def getTexture(self):\r
+        """\r
+        Returns the texture of the bound TextElement\r
+        """\r
+        return self._aloc_text.texture\r
+\r
+    \r
+    #---Properties\r
+    \r
+    text         = property(getText, setText, None, "Text of the object")\r
+    font         = property(getFont, setFont, None, "Font of the object")\r
+    font_size    = property(getFont_size, setFont_size, None, "Font size")\r
+    foreground   = property(getForeground, setForeground, None, "Color/Overlay bitmap of the text")\r
+    centered     = property(getCentered, setCentered, None, "Display the text centered")\r
+    texture_size = property(getTexture_size, None, None, "Size of the used texture")\r
+    texture      = property(getTexture, None, None, "Texture of bound TextElement")\r
+    text_element = property(getTextElement,None , None, "TextElement bound to this class")\r
+\r
+#Optimize critical functions\r
+if psyco and not psyco_optimized:\r
+    psyco.bind(TextElement.createTexture)\r
+    psyco_optimized = True\r